├── icons
├── bad-api.png
├── good-api.png
├── pin-19.png
├── pin-32.png
├── pin-38.png
├── bookmark-16.png
├── bookmark-32.png
├── pin-ticked-19.png
├── pin-ticked-38.png
├── pinboard-32.png
├── pinboard-48.png
├── pinboard-96.png
├── readlater-16.png
└── readlater-32.png
├── options
├── options.css
├── options.js
└── options.html
├── pinboard_menu.css
├── README.md
├── manifest.json
├── pinboard_menu.html
├── pinboard_menu.js
└── background.js
/icons/bad-api.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rami114/pinboard/HEAD/icons/bad-api.png
--------------------------------------------------------------------------------
/icons/good-api.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rami114/pinboard/HEAD/icons/good-api.png
--------------------------------------------------------------------------------
/icons/pin-19.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rami114/pinboard/HEAD/icons/pin-19.png
--------------------------------------------------------------------------------
/icons/pin-32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rami114/pinboard/HEAD/icons/pin-32.png
--------------------------------------------------------------------------------
/icons/pin-38.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rami114/pinboard/HEAD/icons/pin-38.png
--------------------------------------------------------------------------------
/icons/bookmark-16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rami114/pinboard/HEAD/icons/bookmark-16.png
--------------------------------------------------------------------------------
/icons/bookmark-32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rami114/pinboard/HEAD/icons/bookmark-32.png
--------------------------------------------------------------------------------
/icons/pin-ticked-19.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rami114/pinboard/HEAD/icons/pin-ticked-19.png
--------------------------------------------------------------------------------
/icons/pin-ticked-38.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rami114/pinboard/HEAD/icons/pin-ticked-38.png
--------------------------------------------------------------------------------
/icons/pinboard-32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rami114/pinboard/HEAD/icons/pinboard-32.png
--------------------------------------------------------------------------------
/icons/pinboard-48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rami114/pinboard/HEAD/icons/pinboard-48.png
--------------------------------------------------------------------------------
/icons/pinboard-96.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rami114/pinboard/HEAD/icons/pinboard-96.png
--------------------------------------------------------------------------------
/icons/readlater-16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rami114/pinboard/HEAD/icons/readlater-16.png
--------------------------------------------------------------------------------
/icons/readlater-32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rami114/pinboard/HEAD/icons/readlater-32.png
--------------------------------------------------------------------------------
/options/options.css:
--------------------------------------------------------------------------------
1 |
2 | body {
3 | width: 25em;
4 | font-family: "Open Sans Light", sans-serif;
5 | font-size: 0.9em;
6 | font-weight: 300;
7 | }
8 |
9 |
10 | .title {
11 | font-size: 1.2em;
12 | margin-bottom: 0.5em;
13 | }
14 |
15 | label {
16 | float: right;
17 | }
18 |
19 | input {
20 | margin: 0.5em;
21 | width: 200px;
22 | height: 2.5em;
23 | }
24 |
--------------------------------------------------------------------------------
/pinboard_menu.css:
--------------------------------------------------------------------------------
1 | html, body {
2 | margin: 0 0.2em 0.2em 0.2em;
3 | width: auto;
4 | min-width: 200px;
5 | max-width: 400px;
6 | }
7 |
8 | .panel-section-separator {
9 | margin: 0.2em 0;
10 | }
11 |
12 | .good-api {
13 | background-image: url('icons/good-api.png');
14 | background-repeat: no-repeat;
15 | padding-left: 20px;
16 | display: block;
17 | }
18 |
19 | .bad-api {
20 | background-image: url('icons/bad-api.png');
21 | background-repeat: no-repeat;
22 | padding-left: 20px;
23 | display: block;
24 | }
25 |
26 | .hidden {
27 | display: none;
28 | }
--------------------------------------------------------------------------------
/options/options.js:
--------------------------------------------------------------------------------
1 | const apiKeyInput = document.querySelector("#api-key");
2 |
3 | // Helper functions
4 | function storeApiKey() {
5 | browser.storage.local.set({
6 | apiKey: apiKeyInput.value
7 | });
8 | // Will trigger an immediate poll
9 | browser.runtime.sendMessage({'type': 'api-key-saved'});
10 | }
11 |
12 | function updateUI(restoredSettings) {
13 | apiKeyInput.value = restoredSettings.apiKey || "";
14 | }
15 |
16 | function onError(e) {
17 | console.error(e);
18 | }
19 |
20 | // Add hooks to read/write api key as needed
21 | browser.storage.local.get().then(updateUI, onError);
22 | apiKeyInput.addEventListener("blur", storeApiKey);
23 |
24 |
--------------------------------------------------------------------------------
/options/options.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
11 |
12 |
13 |
14 |
Save to Pinboard
15 |
16 |
19 |
22 |
23 |
24 |
25 |
26 |
27 |
Your unread bookmarks
28 |
29 |
30 |
All your bookmarks
31 |
32 |
33 |
Pinboard.in popular
34 |
35 |
36 |
Your saved tab sets
37 |
38 |
39 |
40 |
41 |
Add API key to see status
42 |
43 |
46 |
47 |
Error reaching API
48 |
49 |
52 |
53 |
54 |
55 |
--------------------------------------------------------------------------------
/pinboard_menu.js:
--------------------------------------------------------------------------------
1 | document.addEventListener("click", (e) => {
2 | let querying = browser.tabs.query({currentWindow: true, active: true});
3 | switch(e.target.id) {
4 | case "save-to-pinboard":
5 | querying.then((tabs) => {
6 | for (let tab of tabs) {
7 | let title = tab.title;
8 | let desc = '';
9 | let uri = tab.url;
10 | browser.runtime.sendMessage({
11 | 'type' : 'save-to-pinboard',
12 | 'title' : title,
13 | 'desc' : desc,
14 | 'uri' : uri
15 | });
16 | break;
17 | }
18 | });
19 | break;
20 | case "read-later":
21 | querying.then((tabs) => {
22 | for (let tab of tabs) {
23 | let title = tab.title;
24 | let uri = tab.url;
25 | browser.runtime.sendMessage({
26 | 'type' : 'read-later',
27 | 'title' : title,
28 | 'uri' : uri
29 | });
30 | break;
31 | }
32 | });
33 | break;
34 | case "save-tab-set":
35 | // TODO
36 | break;
37 | case "goto-unread-bookmarks":
38 | browser.tabs.create({active: true, url: "https://pinboard.in/toread"});
39 | break
40 | case "goto-all-bookmarks":
41 | browser.tabs.create({active: true, url: "https://pinboard.in/"});
42 | break
43 | case "goto-pinboard-popular":
44 | browser.tabs.create({active: true, url: "https://pinboard.in/network"});
45 | break
46 | case "goto-saved-tab-sets":
47 | browser.tabs.create({active: true, url: "https://pinboard.in/tabs"});
48 | break
49 | }
50 | });
51 |
52 | // Api functions
53 | function onError(e) {
54 | console.error(e);
55 | }
56 |
57 | // We rely on the status from storage,
58 | function checkApi(settings) {
59 | let apiStatus = (settings.apiStatus && typeof settings.apiStatus !== undefined) ? settings.apiStatus : "no-api-key";
60 | console.log(apiStatus);
61 | let noApiKey = document.getElementById("no-api-key");
62 | let goodApiKey = document.getElementById("good-api-key");
63 | let apiError = document.getElementById("api-error");
64 | let badApiKey = document.getElementById("bad-api-key");
65 | switch(apiStatus) {
66 | case "no-api-key":
67 | noApiKey.classList.remove("hidden");
68 | goodApiKey.classList.add("hidden");
69 | apiError.classList.add("hidden");
70 | badApiKey.classList.add("hidden");
71 | break;
72 | case "bad-api-key":
73 | noApiKey.classList.add("hidden");
74 | goodApiKey.classList.add("hidden");
75 | apiError.classList.add("hidden");
76 | badApiKey.classList.remove("hidden");
77 | break;
78 | case "network-issue":
79 | noApiKey.classList.add("hidden");
80 | goodApiKey.classList.add("hidden");
81 | apiError.classList.remove("hidden");
82 | badApiKey.classList.add("hidden");
83 | break
84 | case "good-api-key":
85 | noApiKey.classList.add("hidden");
86 | goodApiKey.classList.remove("hidden");
87 | apiError.classList.add("hidden");
88 | badApiKey.classList.add("hidden");
89 | break;
90 | }
91 | }
92 |
93 |
94 | browser.storage.local.get().then(checkApi, onError);
95 |
--------------------------------------------------------------------------------
/background.js:
--------------------------------------------------------------------------------
1 | // Globals
2 | var gShowNotifications = true;
3 | // Add-on functions
4 | function onCreated() {
5 | if (browser.runtime.lastError) {
6 | console.log(`Error: ${browser.runtime.lastError}`);
7 | } else {
8 | console.log("Item created successfully");
9 | }
10 | }
11 |
12 | function onError(e) {
13 | console.error(e);
14 | }
15 |
16 | function readSettings(settings) {
17 | // Api-key checks
18 | let apiKey = settings.apiKey;
19 | if (settings.apiKey && settings.apiKey !== undefined) {
20 | apiCheck(apiKey);
21 | }
22 | // We will poll in the background every 5 minutes
23 | browser.alarms.create("poll-api", {
24 | delayInMinutes: 5,
25 | periodInMinutes: 5
26 | });
27 | // Determines if we should show notifications
28 | gShowNotifications = (settings.showNotifications && typeof settings.showNotifications !== undefined) ? settings.showNotifications : false;
29 | browser.menus.update("pinboard-menu-notifications", {
30 | checked: settings.showNotifications
31 | });
32 | }
33 |
34 | function performApiPoll() {
35 | browser.storage.local.get().then((settings) => {
36 | let apiKey = settings.apiKey;
37 | if (settings.apiKey && settings.apiKey !== undefined) {
38 | apiCheck(apiKey);
39 | } else {
40 | browser.storage.local.set({
41 | apiStatus: "no-api-key"
42 | });
43 | }
44 | }, onError);
45 | }
46 |
47 | function apiCheck(apiKey) {
48 | let apiUrl = "https://api.pinboard.in/v1/user/api_token/?auth_token=" + encodeURIComponent(apiKey);
49 | fetch(apiUrl).then((response) => {
50 | if (response.ok) {
51 | browser.storage.local.set({
52 | apiStatus: "good-api-key"
53 | });
54 | } else {
55 | browser.storage.local.set({
56 | apiStatus: "bad-api-key"
57 | });
58 | }
59 | }).catch((error) => {
60 | browser.storage.local.set({
61 | apiStatus: "network-issue"
62 | });
63 | });
64 | }
65 |
66 | function handleAlarm(alarmInfo) {
67 | switch (alarmInfo.name) {
68 | case "poll-api":
69 | performApiPoll();
70 | break;
71 | }
72 | }
73 |
74 | // These two globals are to cache the last tab lookup - otherwise we ping the API consecutively
75 | var cacheTabUrl = '';
76 | var cacheTabXml = null;
77 |
78 | function clearTabCache() {
79 | cacheTabUrl = '';
80 | cacheTabXml = null;
81 | }
82 |
83 | // Our icons for the address bad
84 | const defaultIcons = {"19" : "icons/pin-19.png",
85 | "38" : "icons/pin-38.png"};
86 | const tickIcons = {"19" : "icons/pin-ticked-19.png",
87 | "38" : "icons/pin-ticked-38.png"};
88 |
89 | function checkTabUrl(tab, settings) {
90 | let apiKey = settings.apiKey;
91 | let apiStatus = settings.apiStatus;
92 | if (apiStatus == "good-api-key") {
93 | let checkUrl = 'https://api.pinboard.in/v1/posts/get?auth_token=' + encodeURIComponent(apiKey) + '&url=' + encodeURIComponent(tab.url);
94 | if(checkUrl !== cacheTabUrl) {
95 | fetch(checkUrl)
96 | .then(response => response.text())
97 | .then(str => (new window.DOMParser()).parseFromString(str, "text/xml"))
98 | .then((xml) => {
99 | cacheTabXml = xml;
100 | cacheTabUrl = checkUrl;
101 | let posts = cacheTabXml.getElementsByTagName("post");
102 | if (posts.length > 0) {
103 | // We've seen this, change the icon
104 | browser.pageAction.setIcon({tabId: tab.id, path: tickIcons});
105 | } else {
106 | browser.pageAction.setIcon({tabId: tab.id, path: defaultIcons});
107 | }
108 | });
109 | }
110 | } else {
111 | browser.pageAction.setIcon({tabId: tab.id, path: defaultIcons});
112 | }
113 | }
114 |
115 | // Pinboard functions
116 |
117 | function bookmark(uri, desc, title) {
118 | const dest = 'https://pinboard.in/add?showtags=yes&url=' + encodeURIComponent(uri) + '&description=' + encodeURIComponent(desc) + '&title=' + encodeURIComponent(title);
119 | browser.windows.create({
120 | type: "popup",
121 | height: 350,
122 | width: 725,
123 | url: dest
124 | });
125 | clearTabCache();
126 | }
127 |
128 | function readLater(uri, title) {
129 | const dest = 'https://pinboard.in/add?later=yes&noui=yes&jump=close&url=' + encodeURIComponent(uri) + '&title=' + encodeURIComponent(title);
130 | // We want a window far, far away
131 | // Ugly method to refocus our current window
132 | // Does not gracefully handle when you're not already logged in :(
133 | let getting = browser.windows.getCurrent();
134 | getting.then((windowInfo) => {
135 | // Popup
136 | browser.windows.create({
137 | allowScriptsToClose: true,
138 | //focused: false, // Unsupported by FF?! Sigh.
139 | height: 100,
140 | width: 100,
141 | type: "popup",
142 | url: dest
143 | });
144 | // Refocus
145 | browser.windows.update(windowInfo.id, {
146 | focused: true
147 | });
148 | });
149 | if (gShowNotifications) {
150 | browser.notifications.create({
151 | type: "basic",
152 | message: "Saved to read later",
153 | title: "Pinboard",
154 | iconUrl: browser.extension.getURL("icons/pinboard-32.png")
155 | });
156 | }
157 | clearTabCache();
158 | }
159 | // Creatte the contextual menus (tools and right-click)
160 | browser.menus.create({
161 | id: "pinboard-menu-notifications",
162 | title: "Show notifications",
163 | type: "checkbox",
164 | checked: gShowNotifications,
165 | contexts: ["tools_menu"]
166 | }, onCreated);
167 | // This is primarily to force a sub-menu, but why not link to the source v0v
168 | browser.menus.create({
169 | id: "pinboard-menu-github",
170 | title: "View on Github",
171 | type: "normal",
172 | contexts: ["tools_menu"]
173 | }, onCreated);
174 | browser.menus.create({
175 | id: "link-save-to-pinboard",
176 | title: "Save to Pinboard",
177 | type: "normal",
178 | icons: {
179 | "16": "icons/bookmark-16.png",
180 | "32": "icons/bookmark-32.png"
181 | },
182 | contexts: ["link"]
183 | }, onCreated);
184 | browser.menus.create({
185 | id: "link-read-later",
186 | title: "Read later",
187 | type: "normal",
188 | icons: {
189 | "16": "icons/readlater-16.png",
190 | "32": "icons/readlater-32.png"
191 | },
192 | contexts: ["link"]
193 | }, onCreated);
194 |
195 | // Event listeners
196 |
197 | browser.menus.onClicked.addListener((info, tab) => {
198 | switch (info.menuItemId) {
199 | case "pinboard-menu-notifications":
200 | gShowNotifications = info.checked;
201 | browser.storage.local.set({
202 | showNotifications: info.checked
203 | });
204 | break;
205 | case "pinboard-menu-github":
206 | browser.tabs.create({
207 | active: true,
208 | url: "https://github.com/Rami114/pinboard"
209 | });
210 | break;
211 | case "link-save-to-pinboard":
212 | let desc = (info.selectionText && typeof info.selectionText !== undefined) ? info.selectionText : '';
213 | bookmark(info.linkUrl, desc, info.linkText);
214 | break;
215 | case "link-read-later":
216 | readLater(info.linkUrl, info.linkText);
217 | break;
218 | }
219 | });
220 | browser.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
221 | if (!tab.url.match(/^about:/)) {
222 | browser.pageAction.show(tab.id);
223 | browser.storage.local.get().then((settings) => {checkTabUrl(tab, settings)}, onError);
224 | }
225 | });
226 |
227 | browser.runtime.onMessage.addListener((message) => {
228 | switch (message.type) {
229 | case "save-to-pinboard":
230 | bookmark(message.uri, message.desc, message.title);
231 | break;
232 | case "read-later":
233 | readLater(message.uri, message.title);
234 | break;
235 | case "api-key-saved":
236 | performApiPoll();
237 | break;
238 | }
239 | });
240 |
241 | browser.commands.onCommand.addListener(function(command) {
242 | switch (command) {
243 | case "command-save-to-pinboard":
244 | let querying = browser.tabs.query({currentWindow: true, active: true});
245 | querying.then((tabs) => {
246 | for (let tab of tabs) {
247 | let title = tab.title;
248 | let desc = '';
249 | let uri = tab.url;
250 | bookmark(uri, desc, title);
251 | break;
252 | }
253 | });
254 | break;
255 | case "command-all-bookmarks":
256 | browser.tabs.create({active: true, url: "https://pinboard.in/"});
257 | break;
258 | }
259 | });
260 |
261 | // Fires on startup
262 | browser.storage.local.get().then(readSettings, onError);
263 | // Add alarm listener for api checks
264 | browser.alarms.onAlarm.addListener(handleAlarm);
265 |
--------------------------------------------------------------------------------