Skip to content

Commit 7207912

Browse files
Implement Dropbox export (openstyles#82)
1 parent 711a3af commit 7207912

File tree

7 files changed

+5444
-2
lines changed

7 files changed

+5444
-2
lines changed

_locales/en/messages.json

+17-2
Original file line numberDiff line numberDiff line change
@@ -1182,10 +1182,25 @@
11821182
"description": "Text for link in toolbar pop-up to write a new style for the current URL"
11831183
},
11841184
"bckpInstStyles": {
1185-
"message": "Export styles"
1185+
"message": "Export styles"
11861186
},
11871187
"retrieveBckp": {
1188-
"message": "Import styles"
1188+
"message": "Import styles"
1189+
},
1190+
"bckpDropboxStyles": {
1191+
"message": "Dropbox Export"
1192+
},
1193+
"retrieveDropboxBckp": {
1194+
"message": "Dropbox Import"
1195+
},
1196+
"overwriteFileExport": {
1197+
"message": "Do you want to overwrite an existing file?"
1198+
},
1199+
"exportSavedSuccess": {
1200+
"message": "File saved with success"
1201+
},
1202+
"noFileToImport": {
1203+
"message": "You don't have a file to import."
11891204
},
11901205
"optionsBadgeNormal": {
11911206
"message": "Background color"

dropbox-oauth.html

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6+
<title>OAuth Receiver</title>
7+
8+
<script src="/vendor/dropbox/dropbox.min.js"></script>
9+
<script src="/js/dropbox-auth-receiver.js"></script>
10+
</head>
11+
<body>
12+
</body>
13+
</html>

js/dropbox-auth-receiver.js

+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
'use strict';
2+
3+
/**
4+
got from the old api
5+
@see: https://github.com/dropbox/dropbox-sdk-js/blob/a88a138c0c3260c3537f30f94b003c1cf64f2fbd/examples/javascript/utils.js
6+
*/
7+
function parseQueryString(str) {
8+
let ret = Object.create(null);
9+
10+
if (typeof str !== 'string') {
11+
return ret;
12+
}
13+
14+
str = str.trim().replace(/^(\?|#|&)/, '');
15+
16+
if (!str) {
17+
return ret;
18+
}
19+
20+
str.split('&').forEach(function (param) {
21+
let parts = param.replace(/\+/g, ' ').split('=');
22+
// Firefox (pre 40) decodes `%3D` to `=`
23+
// https://github.com/sindresorhus/query-string/pull/37
24+
let key = parts.shift();
25+
let val = parts.length > 0 ? parts.join('=') : undefined;
26+
27+
key = decodeURIComponent(key);
28+
29+
// missing `=` should be `null`:
30+
// http://w3.org/TR/2012/WD-url-20120524/#collect-url-parameters
31+
val = val === undefined ? null : decodeURIComponent(val);
32+
33+
if (ret[key] === undefined) {
34+
ret[key] = val;
35+
} else if (Array.isArray(ret[key])) {
36+
ret[key].push(val);
37+
} else {
38+
ret[key] = [ret[key], val];
39+
}
40+
});
41+
42+
return ret;
43+
}
44+
45+
window.onload = () => {
46+
47+
let data = {'dropbox_access_token': parseQueryString(location.hash).access_token};
48+
49+
/* this was the only way that worked in keeping a value from page to page with location.href */
50+
/* tried localStorage, but didn't work :/ */
51+
if (typeof browser !== 'undefined') {
52+
browser.storage.local.set(data)
53+
.then(() => {
54+
window.location.href = '/manage.html';
55+
});
56+
} else if (chrome.storage) {
57+
chrome.storage.local.set(data, () => {
58+
window.location.href = '/manage.html';
59+
});
60+
}
61+
}

manage.html

+8
Original file line numberDiff line numberDiff line change
@@ -161,10 +161,14 @@ <h2 class="style-name">
161161
<script src="manage/updater-ui.js" async></script>
162162
<script src="manage/object-diff.js" async></script>
163163
<script src="manage/import-export.js" async></script>
164+
164165
<script src="manage/incremental-search.js" async></script>
165166
<script src="msgbox/msgbox.js" async></script>
166167
<script src="js/sections-equal.js" async></script>
167168
<script src="js/storage-util.js" async></script>
169+
170+
<script src="vendor/dropbox/dropbox-sdk.js" async></script>
171+
<script src="manage/import-export-dropbox.js" async></script>
168172
</head>
169173

170174
<body id="stylus-manage" i18n-dragndrop-hint="dragDropMessage">
@@ -355,6 +359,10 @@ <h2 i18n-text="manageFilters">: <span id="filters-stats"></span></h2>
355359
<button id="file-all-styles" i18n-text="bckpInstStyles"></button>
356360
<button id="unfile-all-styles" i18n-text="retrieveBckp"></button>
357361
</p>
362+
<p>
363+
<button id="sync-dropbox-export" i18n-text="bckpDropboxStyles"></button>
364+
<button id="sync-dropbox-import" i18n-text="retrieveDropboxBckp"></button>
365+
</p>
358366
</details>
359367

360368
<p id="manage-text">

manage/import-export-dropbox.js

+152
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
'use strict';
2+
3+
const DROPBOX_RECEIVER_HTML = '/dropbox-oauth.html';
4+
const DROPBOX_API_KEY = 'uyfixgzre8v1bkg';
5+
const FILENAME = 'stylus.json';
6+
const API_ERROR_STATUS_FILE_NOT_FOUND = 409;
7+
const HTTP_STATUS_CANCEL = 499;
8+
9+
/**
10+
* this was the only way that worked in keeping a value from page to page with location.href (oauth return)
11+
* tried localStorage, but didn't work :/
12+
*/
13+
function hasDropboxAccessToken() {
14+
if (typeof browser !== 'undefined') { /* firefox */
15+
return browser.storage.local.get('dropbox_access_token')
16+
.then(item => {
17+
return item.dropbox_access_token;
18+
});
19+
} else { /* chrome */
20+
return new Promise((resolve, reject) => {
21+
chrome.storage.local.get(['dropbox_access_token'], result => {
22+
resolve(result.dropbox_access_token);
23+
});
24+
});
25+
}
26+
}
27+
28+
function openDropboxOauthPage() {
29+
let client = new Dropbox.Dropbox({clientId: DROPBOX_API_KEY});
30+
let authUrl = client.getAuthenticationUrl(window.location.origin + DROPBOX_RECEIVER_HTML);
31+
32+
window.location.href = authUrl;
33+
}
34+
35+
function uploadFileDropbox(client, stylesText) {
36+
return client.filesUpload({path: '/' + FILENAME, contents: stylesText});
37+
}
38+
39+
40+
$('#sync-dropbox-export').onclick = async () => {
41+
let accessToken = await hasDropboxAccessToken();
42+
if (!accessToken) {
43+
openDropboxOauthPage();
44+
45+
return;
46+
}
47+
48+
let client = new Dropbox.Dropbox({
49+
clientId: DROPBOX_API_KEY,
50+
accessToken: accessToken
51+
});
52+
53+
/**
54+
* check if the file exists, if exists, delete it before upload another
55+
*/
56+
client.filesDownload({path: '/' + FILENAME})
57+
.then(responseGet => {
58+
/** deletes file if user want to */
59+
if (!confirm(t('overwriteFileExport'))) {
60+
return Promise.reject({status: HTTP_STATUS_CANCEL});
61+
}
62+
63+
return client.filesDelete({path: '/' + FILENAME});
64+
})
65+
.then(responseDelete => {
66+
/** file deleted with success, get styles and create a file */
67+
return API.getStyles().then(styles => JSON.stringify(styles, null, '\t'))
68+
})
69+
.then(stylesText => {
70+
/** create file dropbox */
71+
return uploadFileDropbox(client, stylesText);
72+
})
73+
.then(responseSave => {
74+
alert(t('exportSavedSuccess'));
75+
})
76+
.catch(async error => {
77+
/* saving file first time */
78+
if (error.status === API_ERROR_STATUS_FILE_NOT_FOUND) {
79+
let stylesText = await API.getStyles().then(styles => JSON.stringify(styles, null, '\t'));
80+
81+
uploadFileDropbox(client, stylesText)
82+
.then(response => {
83+
alert(t('exportSavedSuccess'));
84+
})
85+
.catch(err => {
86+
console.error(error);
87+
});
88+
89+
return;
90+
}
91+
92+
/* user cancelled the flow */
93+
if (error.status === HTTP_STATUS_CANCEL) {
94+
return;
95+
}
96+
97+
console.error(error);
98+
99+
return;
100+
});
101+
102+
};
103+
104+
$('#sync-dropbox-import').onclick = async () => {
105+
106+
let accessToken = await hasDropboxAccessToken();
107+
if (!accessToken) {
108+
openDropboxOauthPage();
109+
110+
return;
111+
}
112+
113+
let client = new Dropbox.Dropbox({
114+
clientId: DROPBOX_API_KEY,
115+
accessToken: accessToken
116+
});
117+
118+
client.filesDownload({path: '/' + FILENAME})
119+
.then(response => {
120+
let fileBlob = response.fileBlob;
121+
122+
/* it's based on the import-export.js */
123+
const fReader = new FileReader();
124+
fReader.onloadend = event => {
125+
const text = event.target.result;
126+
const maybeUsercss = !/^[\s\r\n]*\[/.test(text) &&
127+
(text.includes('==UserStyle==') || /==UserStyle==/i.test(text));
128+
129+
(!maybeUsercss ?
130+
importFromString(text) :
131+
getOwnTab().then(tab => {
132+
tab.url = URL.createObjectURL(new Blob([text], {type: 'text/css'}));
133+
return API.installUsercss({direct: true, tab})
134+
.then(() => URL.revokeObjectURL(tab.url));
135+
})
136+
);
137+
};
138+
fReader.readAsText(fileBlob, 'utf-8');
139+
})
140+
.catch(error => {
141+
/* no file */
142+
if (error.status === API_ERROR_STATUS_FILE_NOT_FOUND) {
143+
alert(t('noFileToImport'));
144+
145+
return;
146+
}
147+
148+
console.error(err);
149+
});
150+
151+
return;
152+
};

manifest.json

+3
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,9 @@
7777
"js": ["content/install-hook-usercss.js"]
7878
}
7979
],
80+
"web_accessible_resources": [
81+
"/dropbox-oauth.html"
82+
],
8083
"browser_action": {
8184
"default_icon": {
8285
"16": "/images/icon/16w.png",

0 commit comments

Comments
 (0)