Feature/#147 automatic time conversion #150
5
package-lock.json
generated
5
package-lock.json
generated
|
@ -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",
|
||||
|
|
|
@ -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": {
|
||||
|
|
|
@ -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 />
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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';
|
||||
|
|
224
src/javascript/enhancements/timeConversion.js
Normal file
224
src/javascript/enhancements/timeConversion.js
Normal 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;
|
||||
}
|
||||
});
|
||||
}
|
|
@ -36,4 +36,19 @@ function handleKeyToggle(event, isPressed) {
|
|||
} else if (event.key === 'Control') {
|
||||
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;
|
||||
}
|
Loading…
Reference in a new issue