Commit 7978cc29 authored by Iain Murray's avatar Iain Murray
Browse files

First commit

parents
*.zip
*.user.js
MIT License
Copyright (c) 2019 Iain Murray
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
#!/bin/sh
# Needs:
# https://www.imagemagick.org/
# https://www.gnu.org/software/parallel/ (or just delete the "| parallel")
set -e
for sz in 16 19 38 48 96 128 512 ; do
for img in src/icons/*.svg ; do
echo convert -density 600 -background none -geometry "$sz"x"$sz" "$img" "${img%.svg}"_"$sz".png
done
done | parallel
#!/bin/sh
set -e
EXTENSION=drps_arrows
SRC=src
# Set working directory to location of this script
cd $(dirname $(readlink -m "$0"))
VERSION=$(grep '"version":' "$SRC"/manifest.json | sed 's/.*"\([0-9.]*\)".*/\1/')
OUT="$EXTENSION"-"$VERSION".zip
# Stuff specific to this extension:
USERJS="$EXTENSION"-"$VERSION".user.js
[ -e "$SRC"/icons/green_arrows_16.png ] || ./make_icons.sh
(cat <<EOF
// ==UserScript==
// @name DRPS Arrows
// @namespace https://homepages.inf.ed.ac.uk/imurray2/pt/drps_arrows/
EOF
echo '// @version '"$VERSION" ; \
cat <<EOF
// @description Navigate DRPS sessions with ctrl-left, ctrl-right, ctrl-up. Also add links to course codes in DRPS plain text.
// @author Iain Murray
// @match *://www.drps.ed.ac.uk/*
// @grant none
// ==/UserScript==
(function() {
EOF
cat "$SRC"/session.js ; \
sed -n '/session\.js/{ :a; n; p; ba; }' "$SRC"/content.js ; \
echo '})();' ) > "$USERJS"
echo Created "$USERJS"
# Generic zipping:
rm -f "$OUT"
cd "$SRC"
zip -r -FS ../"$OUT" *
echo Created zip for upload to mozilla addons and Chrome store: "$OUT"
"use strict";
// Assumes shared code in session.js included here through the manifest.
let sessionsRegex = /(\/)([0-9][0-9])(-)([0-9][0-9])(\/)/;
function handleContentHide(request, sender, sendResponse) {
chrome.pageAction.setIcon({tabId: sender.tab.id, path: "icons/gray_arrows.svg"});
chrome.pageAction.hide(sender.tab.id);
}
function handleContentShow(request, sender, sendResponse) {
if (sender.url.search(thisSession) >= 0) {
chrome.pageAction.setIcon({tabId: sender.tab.id, path: "icons/gray_arrows.svg"});
chrome.pageAction.show(sender.tab.id);
} else if (sender.url.match(sessionsRegex)) {
if (Number(sender.url.match(sessionsRegex)[0].slice(1, 3)) < Number(thisSession.slice(0, 2))) {
chrome.pageAction.setIcon({tabId: sender.tab.id, path: "icons/red_arrows.svg"});
} else {
chrome.pageAction.setIcon({tabId: sender.tab.id, path: "icons/green_arrows.svg"});
}
chrome.pageAction.show(sender.tab.id);
} else {
handleContentHide(request, sender, sendResponse);
}
}
let SESSIONS;
let BASE_URL;
let TAB;
function handleMenuInit(request, sender, sendResponse) {
let cur;
let prev;
let next;
chrome.tabs.query({active: true, currentWindow: true}, tabs => {
TAB = tabs[0];
BASE_URL = TAB && TAB.url;
if (BASE_URL) {
if (BASE_URL.search(thisSession) >= 0) {
cur = undefined;
prev = sessionString(curStartYear - 1);
next = sessionString(curStartYear + 1);
} else {
let match = BASE_URL.match(sessionsRegex);
cur = thisSession;
let startYear = Number(match[2]);
prev = sessionString(startYear - 1);
next = sessionString(startYear + 1);
}
SESSIONS = {cur: cur, prev: prev, next: next};
sendResponse(SESSIONS);
}
});
}
function handleMenu(request, sender, sendResponse) {
let newURL = BASE_URL.replace(sessionsRegex, '$1' + SESSIONS[request.menu] + '$5');
chrome.tabs.update(TAB.id, {url: newURL});
sendResponse(true);
}
function handleMessage(request, sender, sendResponse) {
const dispatch = {
'src_content_hide': handleContentHide,
'src_content_show': handleContentShow,
'src_menu_init': handleMenuInit,
'src_menu': handleMenu
};
let key = "src_" + request.source;
if (key in dispatch) {
dispatch[key](request, sender, sendResponse);
} else {
console.log("background.js didn't recognize request", request);
}
return true;
};
chrome.runtime.onMessage.addListener(handleMessage);
"use strict";
// Everything beneath here could be in a Userscript (for Greasemonkey, TamperMonkey, etc.)
// ...if concatenate to end of session.js
const URL = location.href;
const match = URL.match(/[0-9]{2}-[0-9]{2}(?=\/)/);
const SESSION = match && match[0];
// Navigate sessions with ctrl-left and ctrl-right
if (SESSION) {
const y1 = Number(SESSION.slice(0, 2));
const prev = sessionString(y1 - 1);
const next = sessionString(y1 + 1);
for (const [key, newSession] of [['Right', next], ['Left', prev], ['Up', thisSession]]) {
document.addEventListener('keyup', e => {
if ((e.code == ('Arrow' + key)) && (e.ctrlKey || e.metaKey)) {
location.href = URL.replace(SESSION, newSession);
}
});
}
}
// Add hyperlinks to course codes and unformatted URLs.
function addLinks(node, regex, urlFn) {
for (let curNode of [... node.childNodes]) {
if (curNode.nodeType == Node.ELEMENT_NODE) {
const skip = ['a', 'style', 'script', 'h1'];
const tag = curNode.tagName.toLowerCase();
if (skip.indexOf(tag) == -1) {
addLinks(curNode, regex, urlFn);
}
} else if (curNode.nodeType == Node.TEXT_NODE) {
let match;
while (match = curNode.textContent.match(regex)) { // not ==
if (match.index == undefined) {
console.log('Problem with matching. Maybe used g in regex?');
return;
}
const nextNode = curNode.splitText(match.index + match[0].length);
curNode.textContent = curNode.textContent.slice(0, match.index);
let linkNode = document.createElement('a');
linkNode.href = urlFn(match[0]);
linkNode.innerHTML = match[0];
node.insertBefore(linkNode, nextNode);
curNode = nextNode;
}
}
}
}
// There's a trade-off with last character in this regex. Will miss some URLs,
// but will spuriously pull in punctuation and formatting less often.
if (SESSION) {
const urlRegex = /(https?:\/\/|www\.)[^ \n]+[a-zA-Z0-9/]/;
addLinks(document.body, urlRegex, x => (x.slice(0, 4) == 'http') ? x : 'http://' + x);
function courseLink(code) {
return 'http://www.drps.ed.ac.uk/' + SESSION + '/dpt/cx' + code.toLowerCase() + '.htm';
}
const courseRegex = /([a-zA-Z]{4}[01][0-9]{4})(?=([^0-9]|$))/;
addLinks(document.body, courseRegex, courseLink);
}
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="2cm" height="2cm" viewBox="10 9.4 2 1.2"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">
<polygon fill="#797979" points="11.2,9.7 11.6,9.7 11.6,9.4 12.0,10.0 11.6,10.6 11.6,10.3 11.2,10.3"/>
<polygon fill="#797979" points="10.8,9.7 10.4,9.7 10.4,9.4 10.0,10.0 10.4,10.6 10.4,10.3 10.8,10.3"/>
</svg>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="2cm" height="2cm" viewBox="10 9.4 2 1.2"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">
<polygon fill="#77c131" points="11.2,9.7 11.6,9.7 11.6,9.4 12.0,10.0 11.6,10.6 11.6,10.3 11.2,10.3"/>
<polygon fill="#77c131" points="10.8,9.7 10.4,9.7 10.4,9.4 10.0,10.0 10.4,10.6 10.4,10.3 10.8,10.3"/>
</svg>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="2cm" height="2cm" viewBox="10 9.4 2 1.2"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">
<polygon fill="#F80404" points="11.2,9.7 11.6,9.7 11.6,9.4 12.0,10.0 11.6,10.6 11.6,10.3 11.2,10.3"/>
<polygon fill="#F80404" points="10.8,9.7 10.4,9.7 10.4,9.4 10.0,10.0 10.4,10.6 10.4,10.3 10.8,10.3"/>
</svg>
{
"manifest_version": 2,
"name": "DRPS navigation",
"version": "0.0.1",
"description": "Adds a navigate button and keyboard short-cuts when browsing the DRPS. Also creates links in course descriptions that contain plain-text URLs or course codes.",
"homepage_url": "http://homepages.inf.ed.ac.uk/imurray2/pt/drps_arrows/",
"permissions": [
"activeTab"
],
"content_scripts": [
{
"matches": ["*://www.drps.ed.ac.uk/*"],
"js": ["session.js", "content.js", "page_messages.js"],
"all_frames": false
}
],
"background": {
"scripts": ["session.js", "background.js"]
},
"icons": {
"16": "icons/red_arrows_16.png",
"48": "icons/red_arrows_48.png",
"96": "icons/red_arrows_96.png",
"128": "icons/red_arrows_128.png",
"512": "icons/red_arrows_512.png"
},
"page_action": {
"browser_style": false,
"default_icon": {
"16": "icons/gray_arrows_16.png",
"19": "icons/gray_arrows_19.png",
"38": "icons/gray_arrows_38.png"
},
"default_title": "DRPS navigation extension",
"default_popup": "menu.html"
}
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style type="text/css">
body {
margin-left: 0;
margin-right: 0;
margin-top: 0.8ex;
margin-bottom: 0.8ex;
padding: 0;
background-color: #fbfbfb;
font: caption;
}
div.item:hover { background-color: #ebebeb; }
div.row {
user-select: none;
-moz-user-select: none;
-webkit-user-select: none;
margin: 0;
padding-left: 1ex;
padding-right: 2ex;
padding-top: 0.5ex;
padding-bottom: 0.5ex;
white-space: nowrap;
}
hr {
color : #858585;
background-color : #858585;
height : 1px;
border: 0 solid #858585;
padding: 0;
margin-left: 0;
margin-right: 0;
margin-top: 0.5ex;
margin-bottom: 0.5ex;
}
</style>
</head>
<body>
<div id="cur_block">
<div class="item row" id="cur"><div style="float: left;">Current Session</div>
<div style="float: right;">Ctrl+↑</div><div style="clear: both;"></div></div>
<hr>
</div>
<div class="item row" id="prev"><div style="float: left;">← Previous</div>
<div style="float: right;">Ctrl+←</div><div style="clear: both;"></div></div>
<div class="item row" id="next"><div style="float: left;">→ Next</div>
<div style="float: right;">Ctrl+→</div><div style="clear: both;"></div></div>
<hr>
<div class="row"><a target="_blank" rel="noopener noreferrer" href="http://homepages.inf.ed.ac.uk/imurray2/pt/drps_arrows/">DRPS arrows extension <span style="font-size: 75%;">(unofficial)</span></a></div>
<script src="menu.js"></script>
</body>
</html>
"use strict";
function formatItem(left, right) {
return `<div style="float: left;">${left}</div><div style="float: right;">${right}</div><div style="clear: both;"></div>`
}
chrome.runtime.sendMessage({source: 'menu_init'}, sessions => {
if (sessions.cur) {
document.getElementById('cur').innerHTML = formatItem('Current session, ' + sessions.cur, '&nbsp;&nbsp;Ctrl+↑');
} else {
document.getElementById('cur_block').hidden = true;
}
document.getElementById('prev').innerHTML = formatItem('' + sessions.prev, 'Ctrl+←');
document.getElementById('next').innerHTML = formatItem('' + sessions.next, 'Ctrl+→');
});
document.addEventListener('click', event => {
let id = event.target.id || event.target.parentNode.id;
if (id != "") {
chrome.runtime.sendMessage({source: 'menu', menu: id}, () => window.close());
}
});
"use strict";
// Unless I catch errors, chrome gives error messages when restarting the
// extension, as it doesn't remove old versions of the content script.
let listeners = [];
function killListeners() {
for (const [e, action] of listeners) {
window.removeEventListener(e, action);
}
listeners = [];
}
const hideMessage = () => { try { chrome.runtime.sendMessage({source: 'content_hide'}); } catch { killListeners(); } };
const showMessage = () => { try { chrome.runtime.sendMessage({source: 'content_show'}); } catch { killListeners(); } };
// The timeout pushes the messages to the end of the queue, so that on extension
// creation the background script has a chance to start up. In Firefox, the
// right page actions then appear. In Chrome the content scripts aren't loaded
// unless the user manually refreshes.
setTimeout(() => {
listeners.push(['pagehide', hideMessage]);
listeners.push(['pageshow', showMessage]);
for (const [e, action] of listeners) {
window.addEventListener(e, action);
}
chrome.runtime.sendMessage({source: 'content_show'});
}, 0);
"use strict";
let curDate = new Date();
let curYear = Number(String(curDate.getFullYear()).slice(-2));
let curStartYear;
// From June, start showing session that starts this year
if (curDate.getMonth() < 6) {
curStartYear = curYear - 1;
} else {
curStartYear = curYear;
}
function sessionString(y1) {
function f(year) {
return ("0" + year).slice(-2);
}
return f(y1) + '-' + f(y1 + 1);
}
let thisSession = sessionString(curStartYear);
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment