Skip to content

Commit

Permalink
Selected text mode (#6)
Browse files Browse the repository at this point in the history
* selection pop up mode, support for time with seconds
* now recognises where time zone is enclosed in brackets (for meetups.twitch.tv)
* features flags available from options page
* version bumped to v0.4.1
  • Loading branch information
codemonkey-uk authored Oct 3, 2020
1 parent fcc5a11 commit 9182bfe
Show file tree
Hide file tree
Showing 7 changed files with 248 additions and 84 deletions.
182 changes: 124 additions & 58 deletions content.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@ var utc_offsets = {
"ACDT": "+10:30", "ACST": "+09:30", "ACT": "-05", "ACWST": "+08:45", "ADT": "-03", "AEDT": "+11", "AEST": "+10", "AFT": "+04:30", "AKDT": "-08", "AKST": "-09", "ALMT": "+06", "AMST": "-03", "ANAT": "+12", "AQTT": "+05", "ART": "-03", "AST": "+03", "AST": "-04", "AWST": "+08", "AZOST": "00", "AZOT": "-01", "AZT": "+04", "BDT": "+08", "BIOT": "+06", "BIT": "-12", "BOT": "-04", "BRST": "-02", "BRT": "-03", "BST": "+01", "BTT": "+06", "CAT": "+02", "CCT": "+06:30", "CDT": "-05", "CEST": "+02", "CET": "+01", "CHADT": "+13:45", "CHAST": "+12:45", "CHOT": "+08", "CHOST": "+09", "CHST": "+10", "CHUT": "+10", "CIST": "-08", "WITA": "+08", "CKT": "-10", "CLST": "-03", "CLT": "-04", "COST": "-04", "COT": "-05", "CST": "-06", "CST": "+08", "CST": "-05", "CT": "-06/UTC-05", "CVT": "-01", "CWST": "+08:45", "CXT": "+07", "DAVT": "+07", "DDUT": "+10", "DFT": "+01", "EASST": "-05", "EAST": "-06", "EAT": "+03", "ECT": "-04", "ECT": "-05", "EDT": "-04", "EEST": "+03", "EET": "+02", "EGST": "00", "EGT": "-01", "WIT": "+09", "ET": "-05", "EST": "-05", "FET": "+03", "FJT": "+12", "FKST": "-03", "FKT": "-04", "FNT": "-02", "GALT": "-06", "GAMT": "-09", "GET": "+04", "GFT": "-03", "GILT": "+12", "GIT": "-09", "GMT": "00", "GST": "-02", "GST": "+04", "GYT": "-04", "HDT": "-09", "HAEC": "+02", "HST": "-10", "HKT": "+08", "HMT": "+05", "HOVST": "+08", "HOVT": "+07", "ICT": "+07", "IDLW": "-12", "IDT": "+03", "IOT": "+03", "IRDT": "+04:30", "IRKT": "+08", "IRST": "+03:30", "IST": "+05:30", "IST": "+01", "IST": "+02", "JST": "+09", "KALT": "+02", "KGT": "+06", "KOST": "+11", "KRAT": "+07", "KST": "+09", "LHST": "+10:30", "LHST": "+11", "LINT": "+14", "MAGT": "+12", "MART": "-09:30", "MAWT": "+05", "MDT": "-06", "MET": "+01", "MEST": "+02", "MHT": "+12", "MIST": "+11", "MIT": "-09:30", "MMT": "+06:30", "MSK": "+03", "MST": "+08", "MST": "-07", "MUT": "+04", "MVT": "+05", "MYT": "+08", "NCT": "+11", "NDT": "-02:30", "NFT": "+11", "NOVT": "+07", "NPT": "+05:45", "NST": "-03:30", "NT": "-03:30", "NUT": "-11", "NZDT": "+13", "NZST": "+12", "OMST": "+06", "ORAT": "+05", "PDT": "-07", "PET": "-05", "PETT": "+12", "PGT": "+10", "PHOT": "+13", "PHT": "+08", "PKT": "+05", "PMDT": "-02", "PMST": "-03", "PONT": "+11", "PST": "-08", "PST": "+08", "PYST": "-03", "PYT": "-04", "RET": "+04", "ROTT": "-03", "SAKT": "+11", "SAMT": "+04", "SAST": "+02", "SBT": "+11", "SCT": "+04", "SDT": "-10", "SGT": "+08", "SLST": "+05:30", "SRET": "+11", "SRT": "-03", "SST": "-11", "SST": "+08", "SYOT": "+03", "TAHT": "-10", "THA": "+07", "TFT": "+05", "TJT": "+05", "TKT": "+13", "TLT": "+09", "TMT": "+05", "TRT": "+03", "TOT": "+13", "TVT": "+12", "ULAST": "+09", "ULAT": "+08", "UTC": "00", "UYST": "-02", "UYT": "-03", "UZT": "+05", "VET": "-04", "VLAT": "+10", "VOLT": "+04", "VOST": "+06", "VUT": "+11", "WAKT": "+12", "WAST": "+02", "WAT": "+01", "WEST": "+01", "WET": "00", "WIB": "+07", "WGST": "-02", "WGT": "-03", "WST": "+08", "YAKT": "+09", "YEKT": "+05"
};
const offset_regex = /([+-])(\d{1,2})(?::?(\d\d))?/;

var local_zone_str_enabled = true;
var time_format = "browser";
var popup_div = null;

// inserts ins_string into mains_string at pos,
// IF the string at pos is not already a match
Expand All @@ -25,36 +23,28 @@ function insert_if(main_string, ins_string, pos)

function localTime2Text(hour, minute)
{
var localZone = local_zone_str_enabled ? " " + Intl.DateTimeFormat().resolvedOptions().timeZone : "";
if (toLocaleTimeStringSupportsLocales() && time_format=="browser")
var localZone = settings.local_zone_str_enabled ? " " + Intl.DateTimeFormat().resolvedOptions().timeZone : "";
if (toLocaleTimeStringSupportsLocales() && settings.time_format=="browser")
{
return browserLocalizedTime(hour, minute) + localZone;
}
return hhmm24h(hour, minute) + localZone;
}

function replaceText (node)
function processText (text, fn)
{
if (node.nodeType === Node.TEXT_NODE) {

// Skip textarea nodes
if (node.parentNode && node.parentNode.nodeName === 'TEXTAREA') {
return;
}

var text = node.nodeValue;
var updatedText = text;
var regex = /\b(\d{1,2})(?:[:h\.]?(\d\d))?\s*([aApP]\.?[mM]\.?)?\s*([A-Z]{2,5})([+-]\d{1,2}:?\d{0,2})?/g;
var regex = /\b(\d{1,2})(?:[:h\.]?(\d\d))?(?:[:m\.]?(\d\d))?\s*([aApP]\.?[mM]\.?)?\s*\(?([A-Z]{2,5})([+-]\d{1,2}:?\d{0,2})?\)?/g;
while(found = regex.exec(text))
{
var hour = parseInt(found[1]);
var minute = found[2] ? parseInt(found[2]) : 0;
var second = found[3] ? parseInt(found[3]) : 0;

var ampm = found[3];
var ampm = found[4];
if (ampm && ampm.toUpperCase().charAt()==="P")
hour += 12;

var zone = found[4];
var zone = found[5];
if (utc_offsets[zone])
{
var zone_offsets = utc_offsets[zone].match(offset_regex);
Expand All @@ -67,9 +57,9 @@ function replaceText (node)
}

// offset on the named time zone
if (found[5])
if (found[6])
{
var offset = found[5].match(offset_regex);
var offset = found[6].match(offset_regex);
if (offset)
{
d = (offset[1]=='-') ? 1 : -1;
Expand Down Expand Up @@ -106,15 +96,42 @@ function replaceText (node)
hour-=24;
}

// found.index
fn( localTime2Text(hour, minute), found );
}
}
}

function childOfId(node,id)
{
do {
if (node.id==id)
return true;
node = node.parentNode;
} while(node);
return false;
}

function replaceText (node)
{
// dont process own pop up
if (childOfId(node,"local_time_popup")) return;

if (node.nodeType === Node.TEXT_NODE) {

// Skip textarea nodes
if (node.parentNode && node.parentNode.nodeName === 'TEXTAREA') return;

var text = node.nodeValue;
var updatedText = text;
processText(text, function (result, found) {
updatedText = insert_if(
updatedText,
" (" + localTime2Text(hour, minute) + ")",
" (" + result + ")",
found.index + found[0].length + (updatedText.length-text.length)
);
)
}
}
if (updatedText!=node.textContent)
);
if (updatedText!=text)
{
node.textContent = updatedText;
}
Expand All @@ -131,56 +148,105 @@ function replaceText (node)
// Now monitor the DOM for additions and substitute emoji into new nodes.
// @see https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver.
const observer = new MutationObserver((mutations) => {
var updateSelection = false;
mutations.forEach((mutation) => {
if (mutation.addedNodes && mutation.addedNodes.length > 0) {
// This DOM change was new nodes being added. Run our substitution
// algorithm on each newly added node.
for (let i = 0; i < mutation.addedNodes.length; i++) {
const newNode = mutation.addedNodes[i];
replaceText(newNode);
}
}
else if (mutation.type==='characterData')
if (childOfId(mutation.target,"local_time_popup")==false)
updateSelection = true;
if (settings.feature_annotations)
{
replaceText(target);
}
if (mutation.addedNodes && mutation.addedNodes.length > 0) {
// This DOM change was new nodes being added. Run our substitution
// algorithm on each newly added node.
for (let i = 0; i < mutation.addedNodes.length; i++) {
const newNode = mutation.addedNodes[i];
replaceText(newNode);
}
}
else if (mutation.type==='characterData')
{
replaceText(mutation.target);
}
}
});
if (updateSelection) checkSelection();
});

function processContent()
function checkSelection(e)
{
// Start the recursion from the body tag.
replaceText(document.body,{characterData: true});
observer.observe(document.body, {
childList: true,
subtree: true
});
}
if (settings.feature_tooltips==false) return;

function gotOptions(item)
{
if (item)
var selection = window.getSelection();
if (selection.isCollapsed)
{
var settings = item.settings;
if (settings)
{
local_zone_str_enabled = settings.local_zone_str_enabled;
time_format = settings.time_format;
}
popup_div.style.display = 'none';
return;
}

// selection starts or ends on the pop up, abort
if (childOfId(selection.anchorNode,"local_time_popup")) return;
if (childOfId(selection.focusNode,"local_time_popup")) return;

var selectionTxt = selection.toString();
var list = document.createElement("ul");
processText(selectionTxt, function (result, found) {
var node = document.createElement("li");
node.textContent = found[0]+ " → " + result;
list.appendChild(node);
});

if (!popup_div)
{
var popup_div_inner = document.createElement("div");
popup_div = document.createElement("div");
popup_div.id = "local_time_popup";
popup_div.style.zIndex = 999; // TODO: make top, check pages zIndex range
popup_div.appendChild(popup_div_inner);
document.body.appendChild(popup_div);
}

if (list.childElementCount > 0)
{
// (outer) div > (inner) div > ul
while (popup_div.lastChild.hasChildNodes())
popup_div.lastChild.removeChild(popup_div.lastChild.lastChild);
popup_div.lastChild.appendChild(list);

var getRange = selection.getRangeAt(0);
var getRect = getRange.getBoundingClientRect();
var w = Math.round(getRect.width);
if (w<200) w=200;
var l = Math.round(getRect.left + getRect.width/2 - w/2);
if (l<0) l=0;
popup_div.style.width = w + "px";
popup_div.style.top = (6+getRect.bottom) + "px";
popup_div.style.left = l + "px";
popup_div.style.display = 'block';
}
else
{
popup_div.style.display = 'none';
}
processContent();
}

function optionsError(err)
function processContent()
{
local_zone_str_enabled=true;
processContent();
if (settings.feature_annotations)
replaceText(document.body,{characterData: true});

observer.observe(document.body, {
childList: true,
subtree: true
});

window.addEventListener('mouseup', checkSelection );
}

function restoreOptions()
{
browser.storage.local.get('settings')
.then(gotOptions,optionsError);
.then(gotOptions)
.then(processContent)
.catch(processContent);
}

function begin()
Expand Down
28 changes: 28 additions & 0 deletions core.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,31 @@

var settings = {
"local_zone_str_enabled": true,
"time_format": "browser",
"feature_annotations": false,
"feature_tooltips": true
};

function gotOptions(loaded)
{
if (loaded)
{
if (loaded.settings)
{
console.log("Loaded Setting: "+JSON.stringify(loaded.settings));
for (const property in settings)
{
if (property in loaded.settings)
{
settings[property] = loaded.settings[property];
}
}
console.log("Setting Restored: "+JSON.stringify(settings));
}
}
return settings;
}

// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toLocaleTimeString
function toLocaleTimeStringSupportsLocales()
{
Expand Down
4 changes: 2 additions & 2 deletions makefile
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
NAME = local_time
VERSION = 0_3_2
VERSION = 0_4_1
BUILD_DIR = release
BUILD_FILE = $(BUILD_DIR)/$(NAME)_$(VERSION).zip
FILES = browser-polyfill.js core.js content.js manifest.json LICENSE icon.svg icon-128.png options.js options.html
FILES = browser-polyfill.js core.js content.js manifest.json LICENSE icon.svg icon-128.png options.js options.html popup.css

.DEFAULT_GOAL := build

Expand Down
5 changes: 3 additions & 2 deletions manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"manifest_version": 2,
"name": "Localise Timezones",
"description": "Converts times to your local time zone",
"version": "0.3.2",
"version": "0.4.1",
"icons": {
"128": "icon-128.png",
"512": "icon.svg"
Expand All @@ -24,7 +24,8 @@
"browser-polyfill.js", "core.js","content.js"
],
"all_frames": true,
"run_at": "document_idle"
"run_at": "document_idle",
"css": ["popup.css"]
}
]
}
16 changes: 13 additions & 3 deletions options.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,15 @@

<body>
<form>
<div>
<input type="checkbox" id="local_zone_str_checkbox" >
<label>Include local time zone name: "<span id="local_zone_str"></span>".</label>
<p>Features: </p>
<div>
<input type="checkbox" id="feature_tooltips_checkbox" >
<label>Pop up time zone conversions for selected text.</label>
</div>
<div>
<input type="checkbox" id="feature_annotations_checkbox" >
<label>Insert timezone conversions everywhere.</label>
</div>
</div>
<p>Preferred time format: </p>
<div>
Expand All @@ -20,6 +26,10 @@
<input type="radio" id="time_format_hhmm24h" name="time_format" value="hhmm24h">
<label for="time_format_hhmm24h">Use HH:MM (24h) - <span id="time_format_hhmm24h_example"></span></label>
</div>
<div>
<input type="checkbox" id="local_zone_str_checkbox" >
<label>Include local time zone name: "<span id="local_zone_str"></span>".</label>
</div>
</form>
<script src="browser-polyfill.js"></script>
<script src="core.js"></script>
Expand Down
Loading

0 comments on commit 9182bfe

Please sign in to comment.