Files
quickasana/background.js

233 lines
4.5 KiB
JavaScript
Raw Normal View History

2022-10-18 23:06:59 -07:00
'use strict';
const reData = new RegExp('^data:(.*?);base64,(.*)$');
2022-10-19 22:53:55 -07:00
const idleIcon = {
path: {
16: 'icons/idle-16.png',
48: 'icons/idle-48.png',
64: 'icons/idle-64.png',
128: 'icons/idle-128.png',
256: 'icons/idle-256.png',
},
}
const activeIcon = {
path: {
16: 'icons/active-16.png',
48: 'icons/active-48.png',
64: 'icons/active-64.png',
128: 'icons/active-128.png',
256: 'icons/active-256.png',
},
}
async function handleClick(tab, e) {
const cfg = await browser.storage.sync.get();
if (!cfg.token || !cfg.workspace || !cfg.assignee) {
await browser.runtime.openOptionsPage();
return;
}
2022-10-18 23:45:10 -07:00
if (e.modifiers.includes('Shift')) {
await sendPaste();
return;
}
2022-10-20 20:55:57 -07:00
if (!tab.url) {
throw 'missing tab.url';
}
2022-10-18 00:02:09 -07:00
const imgURL = await browser.tabs.captureTab(tab.id);
2022-10-18 23:29:16 -07:00
let noteParts = [
`<body>`,
`<a href="${encodeURI(tab.url)}">${escapeHTML(tab.url)}</a>`,
];
2022-10-19 00:13:56 -07:00
const selected = await getSelectedText(tab.id);
2022-10-18 23:29:16 -07:00
if (selected) {
noteParts.push(`\n\n${escapeHTML(selected)}`);
}
noteParts.push('</body>');
2022-10-19 22:53:55 -07:00
await queue('create', {
2022-10-18 23:06:59 -07:00
name: tab.title,
2022-10-18 23:29:16 -07:00
html_notes: noteParts.join(''),
2022-10-18 23:06:59 -07:00
attach: imgURL,
filename: 'screenshot.png',
2022-10-19 22:53:55 -07:00
});
2022-10-18 00:02:09 -07:00
if (!e.modifiers.includes('Command')) {
browser.tabs.remove([tab.id]);
}
2022-10-18 23:06:59 -07:00
}
2022-10-19 00:07:19 -07:00
async function sendPaste() {
const clip = await navigator.clipboard.readText();
2022-10-19 22:53:55 -07:00
await queue('create', {
2022-10-19 00:07:19 -07:00
name: 'Paste',
html_notes: `<body>${escapeHTML(clip)}</body>`,
2022-10-19 22:53:55 -07:00
});
2022-10-19 00:07:19 -07:00
}
2022-10-18 23:06:59 -07:00
let inHandleChange = false;
async function handleChange(e) {
2022-10-18 23:14:37 -07:00
// async functions allow concurrency. Add sketchy mutex.
2022-10-18 23:06:59 -07:00
if (inHandleChange) {
return;
}
try {
inHandleChange = true;
await handleChangeInt(e);
} finally {
inHandleChange = false;
}
}
async function handleChangeInt(e) {
const queue = await browser.storage.local.get();
const keys = Object.getOwnPropertyNames(queue);
if (keys.length == 0) {
2022-10-19 22:53:55 -07:00
browser.browserAction.setIcon(idleIcon);
2022-10-18 23:06:59 -07:00
return;
}
// If some tasks cause issues, make progress on the others over time
const rand = Math.floor(Math.random() * keys.length);
const key = keys[rand];
const task = queue[key];
2022-10-17 09:58:11 -07:00
const cfg = await browser.storage.sync.get();
2022-10-18 23:06:59 -07:00
if (!cfg.token) {
throw 'missing token';
}
2022-10-18 23:06:59 -07:00
const type = key.split('_', 1)[0];
await typeHandlers.get(type)(cfg, task);
await browser.storage.local.remove([key]);
}
async function create(cfg, task) {
if (!cfg.workspace || !cfg.assignee) {
throw 'missing workspace/assignee';
}
2022-10-13 11:09:24 -07:00
const req = {
data: {
2022-10-19 00:03:11 -07:00
workspace: cfg.workspace,
assignee: cfg.assignee,
2022-10-18 23:06:59 -07:00
name: task.name,
2022-10-18 23:14:37 -07:00
html_notes: task.html_notes,
2022-10-13 11:09:24 -07:00
},
};
2022-10-18 00:02:09 -07:00
const createResp = await fetch(
2022-10-13 11:09:24 -07:00
'https://app.asana.com/api/1.0/tasks',
{
method: 'POST',
headers: {
2022-10-19 00:03:11 -07:00
'Authorization': `Bearer ${cfg.token}`,
2022-10-13 11:09:24 -07:00
'Content-Type': 'application/json',
'Accept': 'application/json',
},
2022-10-18 23:06:59 -07:00
credentials: 'omit',
2022-10-13 11:09:24 -07:00
body: JSON.stringify(req),
},
2022-10-17 09:58:11 -07:00
);
2022-10-18 23:06:59 -07:00
if (!createResp.ok) {
throw createResp.statusText;
}
2022-10-17 09:58:11 -07:00
const create = await createResp.json();
2022-10-19 00:03:11 -07:00
if (task.attach) {
2022-10-19 22:53:55 -07:00
await queue('attach', {
2022-10-19 00:03:11 -07:00
gid: create.data.gid,
2022-10-18 23:06:59 -07:00
attach: task.attach,
filename: task.filename,
2022-10-19 22:53:55 -07:00
});
2022-10-18 23:06:59 -07:00
}
}
async function attach(cfg, task) {
const data = new FormData();
2022-10-19 00:13:56 -07:00
const blob = dataURLToBlob(task.attach);
2022-10-18 23:06:59 -07:00
data.append('file', blob, task.filename);
const attachResp = await fetch(
2022-10-18 23:06:59 -07:00
`https://app.asana.com/api/1.0/tasks/${encodeURIComponent(task.gid)}/attachments`,
{
method: 'POST',
headers: {
2022-10-19 00:03:11 -07:00
'Authorization': `Bearer ${cfg.token}`,
},
2022-10-18 23:06:59 -07:00
credentials: 'omit',
body: data,
},
);
2022-10-18 23:06:59 -07:00
if (!attachResp.ok) {
throw attachResp.statusText;
}
2022-10-13 11:09:24 -07:00
}
2022-10-19 22:53:55 -07:00
async function queue(type, details) {
browser.browserAction.setIcon(activeIcon);
const store = {};
store[`${type}_${crypto.randomUUID()}`] = details;
await browser.storage.local.set(store);
}
2022-10-19 00:13:56 -07:00
async function getSelectedText(tabId) {
const selecteds = await browser.tabs.executeScript(
tabId,
{
code: 'getSelection().toString()',
},
);
return selecteds.filter(x => x).join('\n');
}
function dataURLToBlob(url) {
const [_, type, base64] = url.match(reData);
const bytes = atob(base64);
const arr = new Uint8Array(bytes.length);
for (let i = 0; i < bytes.length; i++) {
arr[i] = bytes.charCodeAt(i);
}
return new Blob(
[arr],
{
type: type,
},
);
}
2022-10-18 23:14:37 -07:00
function escapeHTML(unsafe) {
const div = document.createElement('div');
div.innerText = unsafe;
2022-10-19 00:03:11 -07:00
return div.innerHTML.replaceAll('<br>', '\n');
2022-10-18 23:14:37 -07:00
}
2022-10-18 23:06:59 -07:00
const typeHandlers = new Map([
['create', create],
['attach', attach],
]);
2022-10-13 11:09:24 -07:00
browser.browserAction.onClicked.addListener(handleClick);
2022-10-18 23:06:59 -07:00
browser.storage.local.onChanged.addListener(handleChange);
setInterval(handleChange, 10000);