Merge pull request #150 from Serraniel/feature/#147-automatic-time-conversion

Feature/#147 automatic time conversion
This commit is contained in:
Daniel 2020-12-29 14:40:37 +01:00 committed by GitHub
commit 61dfc9c816
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 251 additions and 0 deletions

5
package-lock.json generated
View file

@ -11771,6 +11771,11 @@
"integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=",
"dev": true
},
"spacetime": {
"version": "6.12.2",
"resolved": "https://registry.npmjs.org/spacetime/-/spacetime-6.12.2.tgz",
"integrity": "sha512-w0St4Q9X8KtuZ/JY8+FM8a4hMrAoNNUWQCt9UQQAUzwk8eDW5wrGh4SaNvEg+9cjLF++vixm6SgJyC6F7ALF/A=="
},
"sparkles": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/sparkles/-/sparkles-1.0.1.tgz",

View file

@ -34,6 +34,7 @@
"homepage": "https://github.com/Serraniel/AniwatchPlus#readme",
"dependencies": {
"color": "^3.1.3",
"spacetime": "^6.12.2",
"uuid": "^8.3.2"
},
"devDependencies": {

View file

@ -16,6 +16,9 @@
<input type="checkbox" id="websiteDisplayQuickSearch" data-default-value="true" />
<label for="websiteDisplayQuickSearch">Enable Quick Search</label><br />
<input type="checkbox" id="websiteAutoTimeConversion" data-default-value="true" />
<label for="websiteAutoTimeConversion">Convert to local time</label><br />
<input type="checkbox" id="websiteShowNotificationsCountInTab" data-default-value="true" />
<label for="websiteShowNotificationsCountInTab">Show notification counter in browser tab</label><br />

View file

@ -9,6 +9,7 @@ import { init as fontColor } from './enhancements/fontColor';
import { init as languageDisplay } from './enhancements/languageDisplay';
import { init as notifications } from './enhancements/notifications';
import { init as quickSearch } from './enhancements/quickSearch';
import { init as timeConversion } from './enhancements/timeConversion';
import { init as watch2getherChat } from './enhancements/watch2getherChat';
// css
import { init as cssEnhancements } from './enhancements/cssEnhancements';
@ -26,6 +27,7 @@ fontColor();
languageDisplay();
notifications();
quickSearch();
timeConversion();
watch2getherChat();
// css

View file

@ -3,6 +3,7 @@ import { assigned } from "../utils/helpers";
// website
export const SETTINGS_websiteDisplayQuickSearch = 'websiteDisplayQuickSearch';
export const SETTINGS_websiteAutoTimeConversion = 'websiteAutoTimeConversion';
export const SETTINGS_websiteShowNotificationsCountInTab = 'websiteShowNotificationsCountInTab';
export const SETTINGS_websiteHideUnusedTabs = 'websiteHideUnusedTabs';
export const SETTINGS_websiteOptimizeListAppearance = 'websiteOptimizeListAppearance';

View file

@ -0,0 +1,224 @@
import spacetime from 'spacetime';
import { getGlobalConfiguration, SETTINGS_websiteAutoTimeConversion } from '../configuration/configuration';
import * as core from '../utils/aniwatchCore';
import * as helper from '../utils/helpers';
const __alteredNodes = [];
const DAYS = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'];
export function init() {
getGlobalConfiguration().getProperty(SETTINGS_websiteAutoTimeConversion, value => {
if (value) {
// The regexp pattern matches anything except the airing page.
// This is because we would have to restructure the complete site to update time data.
// Additionally, there is a big hint that all data would be UTC+1
core.runAfterLoad(() => {
updateTimestamps(document.documentElement);
}, "^/(?!airing).*$");
core.runAfterLocationChange(() => {
updateTimestamps(document.documentElement);
}, "^/(?!airing).*$");
core.registerScript(node => {
updateTimestamps(node);
}, "^/(?!airing).*$");
}
});
}
function getSpaceDateTimeFormat(use24Format) {
return `${getSpaceDateFormat()} ${getSpaceTimeFormat(use24Format)}`;
}
function getSpaceTimeFormat(use24Format) {
if (use24Format) {
return '{time-24}';
}
return '{time}';
}
function getSpaceDateFormat() {
return '{date}. {month-short} {year}';
}
function tryUpdateDateTime(node) {
const REG_DATETIME = /(\d{2}(\/|\.)){2}\d{4} *\d?\d:\d{2}( (AM|PM))?/g;
const REG_TIME = /\d?\d:\d{2}/;
const REG_AMPM = /\s(am|pm)/i;
let hits = Array.from(node.textContent.matchAll(REG_DATETIME), match => match[0]);
if (hits.length === 0) {
return false;
}
hits.forEach(hit => {
let use24Format = false;
let processedStr = hit
// string must be converted into 12h format
if (processedStr.search(REG_AMPM) < 0) {
let timeStr = processedStr.match(REG_TIME)[0];
let hm = timeStr.split(':');
let hour = parseInt(hm[0]);
if (hour >= 12) {
timeStr = timeStr.replace(`${hour}:`, `${hour - 12}:`);
timeStr += 'pm';
}
else {
timeStr += 'am';
}
processedStr = processedStr.replace(REG_TIME, timeStr);
use24Format = true;
}
// if time has a space before am/pm, this has to be removed for spacetime
processedStr = processedStr.replace(REG_AMPM, '$1');
let datetime = spacetime(processedStr, 'UTC+1', { dmy: true });
datetime = datetime.goto(spacetime().tz);
let replaceText = datetime.format(getSpaceDateTimeFormat(use24Format));
node.textContent = node.textContent.replace(hit, replaceText);
});
return true;
}
function tryUpdateDate(node) {
const REG_DATE = /(\d{2}(\/|\.)){2}\d{4}/g;
let hits = Array.from(node.textContent.matchAll(REG_DATE), match => match[0]);
if (hits.length === 0) {
return false;
}
hits.forEach(hit => {
let datetime = spacetime(hit, 'UTC+1', { dmy: true });
datetime = datetime.goto(spacetime().tz);
let replaceText = datetime.format(getSpaceDateFormat());
node.textContent = node.textContent.replace(hit, replaceText);
});
return true;
}
function tryUpdateTime(node) {
const REG_TIME = /\d?\d:\d{2}( (AM|PM))?/g;
const REG_AMPM = /\s(am|pm)/i;
let hits = Array.from(node.textContent.matchAll(REG_TIME), match => match[0]);
if (hits.length === 0) {
return false;
}
hits.forEach(hit => {
let use24Format = false;
let processedStr = hit
// string must be converted into 12h format
if (processedStr.search(REG_AMPM) < 0) {
let timeStr = processedStr.match(REG_TIME)[0];
let hm = timeStr.split(':');
let hour = parseInt(hm[0]);
if (hour >= 12) {
timeStr = timeStr.replace(`${hour}:`, `${hour - 12}:`);
timeStr += 'pm';
}
else {
timeStr += 'am';
}
processedStr = processedStr.replace(REG_TIME, timeStr);
use24Format = true;
}
// if time has a space before am/pm, this has to be removed for spacetime
processedStr = processedStr.replace(REG_AMPM, '$1');
let datetime = spacetime();
datetime = datetime.goto('UTC+1');
datetime = datetime.time(processedStr);
datetime = datetime.goto(spacetime().tz);
let replaceText = datetime.format(getSpaceTimeFormat(use24Format));
node.textContent = node.textContent.replace(hit, replaceText);
// SPECIAL CASE: Anime has the day written in broadcast bade. This may be different in another timezone
let tzMeta = spacetime().timezone();
let originalH = datetime.hour() - tzMeta.current.offset + 1;
let dOffset = 0;
// we moved to next day
if (originalH < 0) {
dOffset = 1;
}
// we moved to previous day
else if (originalH > 24) {
dOffset = -1;
}
// if day changed
if (dOffset != 0) {
let dayNode = node.parentNode.previousElementSibling;
if (helper.assigned(dayNode)) {
for (let i = 0; i < DAYS.length; i++) {
if (dayNode.textContent.indexOf(DAYS[i]) >= 0) {
dayNode.textContent = dayNode.textContent.replace(DAYS[i], DAYS[(i + DAYS.length + dOffset) % DAYS.length]); // add DAYS.length to avoid negative numbers in the modulo operation
break;
}
}
}
}
});
return true;
}
function tryUpdateTimeZone(node) {
const HINT_UTC = 'UTC+1';
if (node.textContent === HINT_UTC) {
let tzMeta = spacetime().timezone();
node.textContent = `${tzMeta.name} (UTC${tzMeta.current.offset >= 0 ? '+' : ''}${tzMeta.current.offset})`;
}
}
function updateTimestamps(node) {
let nodes = helper.findTextNodes(node);
nodes.forEach(node => {
// avoid double updates
if (__alteredNodes.indexOf(node) >= 0) {
return;
}
if (tryUpdateDateTime(node)) {
__alteredNodes.push(node);
return;
}
if (tryUpdateDate(node)) {
__alteredNodes.push(node);
return;
}
if (tryUpdateTime(node)) {
__alteredNodes.push(node);
return;
}
if (tryUpdateTimeZone(node)) {
__alteredNodes.push(node);
return;
}
});
}

View file

@ -37,3 +37,18 @@ function handleKeyToggle(event, isPressed) {
isCtrlPressed = isPressed;
}
}
export function findTextNodes(baseNode) {
if (!assigned(baseNode)) {
baseNode = document.documentElement;
}
let walker = document.createTreeWalker(baseNode, NodeFilter.SHOW_TEXT, null, false);
let node;
let results = [];
while (node = walker.nextNode()) {
results.push(node);
}
return results;
}