__MSG_AdvancedAccountList__
@@ -143,7 +95,7 @@
import { onMounted, onUnmounted, ref } from 'vue'
import { tsLogger } from '@statslib/mzts-logger';
import { tsStore } from '@statslib/mzts-store';
- import { TS_prefs } from "@statslib/mzts-options";
+ import { tsPrefs } from "@statslib/mzts-options";
import { tsCoreUtils } from '@statslib/mzts-statscore.utils';
import AdvancedAccountList from '../AdvancedAccountList.vue';
@@ -160,7 +112,7 @@
input.addEventListener('change', somethingChanged);
});
document.getElementById('first_day_week').addEventListener('change', somethingChanged);
- let archive_accounts = await TS_prefs.getPref("accounts_adv_settings");
+ let archive_accounts = await tsPrefs.getPref("accounts_adv_settings");
let all_accounts = await tsCoreUtils.getAccountsList();
accounts_adv_settings.value = await tsCoreUtils.mergeAccountsAdvSettings(all_accounts, archive_accounts);
tsLog.log("accounts_adv_settings: " + JSON.stringify(accounts_adv_settings.value));
@@ -171,12 +123,6 @@
tsLog.log("onUnmounted");
});
- function setDefaultDatePickerLocale() {
- const datepicker = document.getElementById('datepicker_locale');
- datepicker.value = navigator.language;
- datepicker.dispatchEvent(new Event('change', { 'bubbles': true }));
- }
-
async function somethingChanged() {
new_changes.value = true;
emit('new_changes', new_changes.value);
@@ -190,7 +136,7 @@
}
function accountListChanged(accountsList) {
- TS_prefs.setPref("accounts_adv_settings", accountsList);
+ tsPrefs.setPref("accounts_adv_settings", accountsList);
somethingChanged();
}
diff --git a/src/components/options_tabs/OPTAB_BusinessDays.vue b/src/components/options_tabs/OPTAB_BusinessDays.vue
new file mode 100644
index 00000000..7a6ce8a5
--- /dev/null
+++ b/src/components/options_tabs/OPTAB_BusinessDays.vue
@@ -0,0 +1,369 @@
+
+
+
+
+
+
+
+
+
+ __MSG_Save__
+ __MSG_Cancel__
+
+
+
+
+
+
+
+
+ __MSG_Save__
+ __MSG_Cancel__
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/components/options_tabs/OPTAB_CustomIds.vue b/src/components/options_tabs/OPTAB_CustomIds.vue
index dd2cef2d..b3e49edf 100644
--- a/src/components/options_tabs/OPTAB_CustomIds.vue
+++ b/src/components/options_tabs/OPTAB_CustomIds.vue
@@ -62,7 +62,7 @@
diff --git a/src/components/tabs/TAB_Yesterday.vue b/src/components/tabs/TAB_Yesterday.vue
index 3b396669..385ab59d 100644
--- a/src/components/tabs/TAB_Yesterday.vue
+++ b/src/components/tabs/TAB_Yesterday.vue
@@ -19,12 +19,14 @@
-->
+
@@ -32,7 +34,7 @@
-
__MSG_FolderLocation__
+
__MSG_FolderLocation__
@@ -58,20 +60,24 @@
diff --git a/statslib/chartjs-lib/plugin-timegraph-vertical-line.js b/statslib/chartjs-lib/plugin-timegraph-vertical-line.js
new file mode 100644
index 00000000..d3172cb8
--- /dev/null
+++ b/statslib/chartjs-lib/plugin-timegraph-vertical-line.js
@@ -0,0 +1,44 @@
+/*
+ * ThunderStats [https://micz.it/thunderbird-addon-thunderstats-your-thunderbird-statistics/]
+ * Copyright (C) 2024 Mic (m@micz.it)
+
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+export const tsVerticalLinePlugin = {
+ id: "tsVerticalLinePlugin",
+ beforeDraw: (chart) => {
+ const drawVerticalLineAt = chart.options.plugins.tsVerticalLinePlugin.drawVerticalLineAt;
+ const verticalLineColor = chart.options.plugins.tsVerticalLinePlugin.verticalLineColor;
+ const xScale = chart.scales.x;
+ const yScale = chart.scales.y;
+ const ctx = chart.ctx;
+ // Find the index of the tick where you want to draw the vertical line
+ const index = chart.data.labels.indexOf(drawVerticalLineAt);
+
+ if (index !== -1) {
+ const x = xScale.getPixelForValue(index);
+
+ ctx.save();
+ ctx.beginPath();
+ ctx.moveTo(x, yScale.top);
+ ctx.lineTo(x, yScale.bottom);
+ ctx.lineWidth = 1;
+ ctx.strokeStyle = verticalLineColor;
+ ctx.setLineDash([5, 5])
+ ctx.stroke();
+ ctx.restore();
+ }
+ }
+}
\ No newline at end of file
diff --git a/statslib/mzts-export.js b/statslib/mzts-export.js
new file mode 100644
index 00000000..09779b95
--- /dev/null
+++ b/statslib/mzts-export.js
@@ -0,0 +1,194 @@
+/*
+ * ThunderStats [https://micz.it/thunderbird-addon-thunderstats-your-thunderbird-statistics/]
+ * Copyright (C) 2024 Mic (m@micz.it)
+
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+import { tsCoreUtils } from '@statslib/mzts-statscore.utils';
+
+export const tsExport = {
+
+ export: {
+ correspondents: {
+ type: 'correspondents',
+ name: 'Correspondents',
+ },
+ time_emails: {
+ type: 'time_emails',
+ name: 'Time Emails',
+ },
+ daily_mails: {
+ type: 'daily_mails',
+ name: 'Daily Emails',
+ },
+ },
+
+ getExportPrefix() {
+ return 'ThunderStats_Export_';
+ },
+
+ /* https://stackoverflow.com/a/58769574 */
+ arrayToCsv(data) {
+ if (!data || data.length === 0) {
+ return '';
+ }
+
+ const array = [Object.keys(data[0])].concat(data)
+
+ return array.map(it => {
+ return Object.values(it).toString()
+ }).join('\n');
+ },
+
+ /* https://stackoverflow.com/a/68146412 */
+ /* downloadBlob(csv, 'export.csv', 'text/csv;charset=utf-8;')*/
+ downloadBlob(content, filename, contentType) {
+ // Create a blob
+ var blob = new Blob([content], { type: contentType });
+ var url = URL.createObjectURL(blob);
+
+ // Create a link to download it
+ var pom = document.createElement('a');
+ pom.href = url;
+ pom.setAttribute('download', filename);
+ pom.click();
+ },
+
+ downloadCSV(data, export_name) {
+ let output_filename = tsExport.getExportPrefix() + tsExport.makeSafeForFilePath(export_name) + '_' + tsExport.getCurrentTimestamp() + '.csv';
+ this.downloadBlob(this.arrayToCsv(data), output_filename, 'text/csv;charset=utf-8;');
+ },
+
+ makeSafeForFilePath(input) {
+ // Replace unsafe characters with an underscore
+ return input
+ .replace(/[\/\\:*?"<>|]/g, '_') // Replace special characters
+ .replace(/[^\w\-_.]/g, '_') // Replace non-alphanumeric characters (except underscore, period, and hyphen)
+ .replace(/_+/g, '_') // Replace multiple consecutive underscores with a single one
+ .trim(); // Remove whitespace from the beginning and end
+ },
+
+ getCurrentTimestamp() {
+ const now = new Date();
+
+ const year = now.getFullYear();
+ const month = String(now.getMonth() + 1).padStart(2, '0'); // getMonth() returns 0-11, so we add 1
+ const day = String(now.getDate()).padStart(2, '0');
+ const hours = String(now.getHours()).padStart(2, '0');
+ const minutes = String(now.getMinutes()).padStart(2, '0');
+ const seconds = String(now.getSeconds()).padStart(2, '0');
+
+ // Combine all parts into the desired format
+ return `${year}${month}${day}${hours}${minutes}${seconds}`;
+ },
+
+ mergeRecipientsAndSenders(recipients, senders) {
+ const correspondents = {};
+
+ // Process recipients
+ for (const [email, details] of Object.entries(recipients)) {
+ if (!correspondents[email]) {
+ correspondents[email] = {
+ sent: 0,
+ received: details.count,
+ name: details.name
+ };
+ } else {
+ correspondents[email].received = details.count;
+ }
+ }
+
+ // Process senders
+ for (const [email, details] of Object.entries(senders)) {
+ if (!correspondents[email]) {
+ correspondents[email] = {
+ sent: details.count,
+ received: 0,
+ name: details.name
+ };
+ } else {
+ correspondents[email].sent = details.count;
+ }
+ }
+
+ return correspondents;
+ },
+
+ transformCorrespondentsJsonToArray(json) {
+ const resultArray = [];
+ // console.log(">>>>>>>>>>>>> transformCorrespondentsJsonToArray json: " + JSON.stringify(json));
+ const nameKey = browser.i18n.getMessage('Name');
+ const mailKey = browser.i18n.getMessage('Mail');
+ const sentKey = browser.i18n.getMessage('TimeChart.Sent');
+ const rcvdKey = browser.i18n.getMessage('TimeChart.Rcvd');
+
+ for (const email in json) {
+ // console.log(">>>>>>>>>>>>> transformCorrespondentsJsonToArray email: " + JSON.stringify(email));
+ if (json.hasOwnProperty(email)) {
+ const obj = {};
+ obj[nameKey] = json[email].name;
+ obj[mailKey] = email;
+ obj[sentKey] = json[email].sent;
+ obj[rcvdKey] = json[email].received;
+
+ resultArray.push(obj);
+ }
+ }
+
+ return resultArray;
+ },
+
+ transformDailyMailsJsonToArray(json) {
+ let resultArray = [];
+
+ const dateKey = browser.i18n.getMessage('Date');
+ const sentKey = browser.i18n.getMessage('TimeChart.Sent');
+ const rcvdKey = browser.i18n.getMessage('TimeChart.Rcvd');
+
+ for (let date in json) {
+ let mailData = json[date];
+ const obj = {};
+ let formatted_date = tsCoreUtils.getManyDaysLabel(date);
+ obj[dateKey] = formatted_date[1];
+ obj[sentKey] = mailData.sent;
+ obj[rcvdKey] = mailData.received;
+
+ resultArray.push(obj);
+ }
+
+ return resultArray;
+ },
+
+ transformTimeMailsJsonToArray(json) {
+ let resultArray = [];
+
+ const timeKey = browser.i18n.getMessage('TimeChart.Time');
+ const sentKey = browser.i18n.getMessage('TimeChart.Sent');
+ const rcvdKey = browser.i18n.getMessage('TimeChart.Rcvd');
+
+ const transformedArray = [];
+
+ for (let hour in json) {
+ const obj = {};
+ obj[timeKey] = parseInt(hour);
+ obj[sentKey] = json[hour].sent;
+ obj[rcvdKey] = json[hour].received;
+
+ resultArray.push(obj);
+ }
+
+ return resultArray;
+ },
+}
\ No newline at end of file
diff --git a/statslib/mzts-logger.js b/statslib/mzts-logger.js
index 5ef24dbb..cacf4900 100644
--- a/statslib/mzts-logger.js
+++ b/statslib/mzts-logger.js
@@ -20,20 +20,19 @@ export class tsLogger {
do_debug = false;
prefix = "";
+
constructor(prefix,do_debug) {
this.do_debug = do_debug;
- this.prefix = "[tsLogger|" + prefix + "] ";
+ this.prefix = "[ThunderStats Logger | " + prefix + "] ";
}
log(msg, do_debug = -1) {
if(do_debug !== -1) this.do_debug = do_debug;
if(this.do_debug === true) console.log(this.prefix + msg);
}
- error(msg, do_debug = -1) {
- if(do_debug !== -1) this.do_debug = do_debug;
- if(this.do_debug === true) console.error(this.prefix + msg);
+ error(msg) {
+ console.error(this.prefix + msg);
}
- warn(msg, do_debug = -1) {
- if(do_debug !== -1) this.do_debug = do_debug;
- if(this.do_debug === true) console.warn(this.prefix + msg);
+ warn(msg) {
+ console.warn(this.prefix + msg);
}
};
\ No newline at end of file
diff --git a/statslib/mzts-options-default.js b/statslib/mzts-options-default.js
index 033d280c..279c03b8 100644
--- a/statslib/mzts-options-default.js
+++ b/statslib/mzts-options-default.js
@@ -3,6 +3,7 @@ export const prefs_default = {
'_many_days': 7,
'_involved_num': 10,
'today_time_graph_show_yesterday': true,
+ 'today_time_graph_do_no_show_future': true,
'_time_graph_progressive': false,
'inbox0_openFolderInFirstTab': false,
'startup_account': 0,
@@ -16,4 +17,15 @@ export const prefs_default = {
'include_archive_multi_account': true,
'filter_duplicates_multi_account': false,
'accounts_adv_settings': {},
+ 'bday_use_last_business_day': false,
+ 'bday_default_only': false,
+ 'bday_weekdays_0': false, //Sunday
+ 'bday_weekdays_1': true, //Monday
+ 'bday_weekdays_2': true, //Tuesday
+ 'bday_weekdays_3': true, //Wednesday
+ 'bday_weekdays_4': true, //Thursday
+ 'bday_weekdays_5': true, //Friday
+ 'bday_weekdays_6': false, //Saturday
+ 'bday_custom_days': [],
+ 'bday_easter': true,
}
\ No newline at end of file
diff --git a/statslib/mzts-options.js b/statslib/mzts-options.js
index 13e3012c..c37ff177 100644
--- a/statslib/mzts-options.js
+++ b/statslib/mzts-options.js
@@ -12,7 +12,7 @@
import { prefs_default } from './mzts-options-default.js';
-export const TS_prefs = {
+export const tsPrefs = {
logger: console,
@@ -23,22 +23,22 @@ export const TS_prefs = {
switch (element.type) {
case 'checkbox':
options[element.id] = element.checked;
- TS_prefs.logger.log('Saving option: ' + element.id + ' = ' + element.checked);
+ tsPrefs.logger.log('Saving option: ' + element.id + ' = ' + element.checked);
break;
case 'number':
options[element.id] = element.valueAsNumber;
- TS_prefs.logger.log('Saving option: ' + element.id + ' = ' + element.valueAsNumber);
+ tsPrefs.logger.log('Saving option: ' + element.id + ' = ' + element.valueAsNumber);
break;
case 'text':
options[element.id] = element.value.trim();
- TS_prefs.logger.log('Saving option: ' + element.id + ' = ' + element.value);
+ tsPrefs.logger.log('Saving option: ' + element.id + ' = ' + element.value);
break;
default:
if (element.tagName === 'SELECT') {
options[element.id] = element.value;
- TS_prefs.logger.log('Saving option: ' + element.id + ' = ' + element.value);
+ tsPrefs.logger.log('Saving option: ' + element.id + ' = ' + element.value);
}else{
- TS_prefs.logger.log('Unhandled input type:', element.type);
+ tsPrefs.logger.log('Unhandled input type:', element.type);
}
}
browser.storage.sync.set(options);
@@ -47,7 +47,7 @@ export const TS_prefs = {
async setPref(pref_id, value){
let obj = {};
obj[pref_id] = value;
- TS_prefs.logger.log('Saving option: ' + pref_id + ' = ' + JSON.stringify(value));
+ tsPrefs.logger.log('Saving option: ' + pref_id + ' = ' + JSON.stringify(value));
browser.storage.sync.set(obj)
},
@@ -55,7 +55,7 @@ export const TS_prefs = {
let obj = {};
obj[pref_id] = prefs_default[pref_id];
let prefs = await browser.storage.sync.get(obj)
- TS_prefs.logger.log("getPref prefs: " + JSON.stringify(prefs));
+ tsPrefs.logger.log("getPref prefs: " + JSON.stringify(prefs));
return prefs[pref_id];
},
@@ -66,7 +66,7 @@ export const TS_prefs = {
obj[pref_id] = prefs_default[pref_id];
});
let prefs = await browser.storage.sync.get(obj)
- TS_prefs.logger.log("getPrefs: " + JSON.stringify(prefs));
+ tsPrefs.logger.log("getPrefs: " + JSON.stringify(prefs));
let result = {};
pref_ids.forEach(pref_id => {
result[pref_id] = prefs[pref_id];
@@ -77,7 +77,7 @@ export const TS_prefs = {
restoreOptions() {
function setCurrentChoice(result) {
- TS_prefs.logger.log("restoreOptions: " + JSON.stringify(result));
+ tsPrefs.logger.log("restoreOptions: " + JSON.stringify(result));
document.querySelectorAll(".option-input").forEach(element => {
const defaultValue = prefs_default[element.id];
switch (element.type) {
@@ -101,14 +101,14 @@ export const TS_prefs = {
element.selectedIndex = -1;
}
}else{
- TS_prefs.logger.log('Unhandled input type:', element.type);
+ tsPrefs.logger.log('Unhandled input type:', element.type);
}
}
});
}
function onError(error) {
- TS_prefs.logger.log(`Error: ${error}`);
+ tsPrefs.logger.log(`Error: ${error}`);
}
let getting = browser.storage.sync.get(prefs_default);
diff --git a/statslib/mzts-statscore.js b/statslib/mzts-statscore.js
index 5d8c3fab..2129bda2 100644
--- a/statslib/mzts-statscore.js
+++ b/statslib/mzts-statscore.js
@@ -19,7 +19,7 @@
import { tsLogger } from "./mzts-logger";
import { tsCoreUtils } from "./mzts-statscore.utils";
import { tsUtils } from "./mzts-utils";
-import { TS_prefs } from "./mzts-options";
+import { tsPrefs } from "./mzts-options";
export class thunderStastsCore {
@@ -32,7 +32,7 @@ export class thunderStastsCore {
constructor(options = {}) {
this.do_debug = options.hasOwnProperty('do_debug') ? options.do_debug : false;
this.tsLog = new tsLogger("thunderStastsCore",this.do_debug);
- TS_prefs.logger = this.tsLog;
+ tsPrefs.logger = this.tsLog;
this._involved_num = options.hasOwnProperty('_involved_num') ? options._involved_num : 10;
this._many_days = options.hasOwnProperty('_many_days') ? options._many_days : 7;
this.accounts_adv_settings = options.hasOwnProperty('accounts_adv_settings') ? options.accounts_adv_settings : [];
@@ -64,20 +64,8 @@ export class thunderStastsCore {
let yesterday_midnight = new Date();
yesterday_midnight.setDate(yesterday_midnight.getDate() - 1);
yesterday_midnight.setHours(0, 0, 0, 0);
- let yesterday = new Date();
- yesterday.setDate(yesterday.getDate() - 1);
- yesterday.setHours(23, 59, 59, 999);
- //yesterday.setHours(new Date().getHours(), new Date().getMinutes(), new Date().getSeconds(), new Date().getMilliseconds());
- // console.log(">>>>>>>>>>>>>> getToday_YesterdayData yesterday_midnight: " + JSON.stringify(yesterday_midnight));
- // console.log(">>>>>>>>>>>>>> getToday_YesterdayData yesterday: " + JSON.stringify(yesterday));
- let fromDate = new Date(yesterday_midnight);
- let toDate = new Date(yesterday);
- // let fromDate = new Date(Date.now() - 56 * (24 * 60 * 60 * 1000)); // FOR TESTING ONLY
- // let toDate = new Date(Date.now() - (24 * 60 * 60 * 1000)) // FOR TESTING ONLY
-
- let filter_duplicates = await tsCoreUtils.getFilterDuplicatesPreference(account_id);
- return this.getCountStatsData(fromDate, toDate, account_id, account_emails, count_data_to_current_time, filter_duplicates);
+ return this.getToday_SingleDayData(yesterday_midnight, account_id, account_emails, count_data_to_current_time);
}
async getToday_manyDaysData(account_id = 0, account_emails = []) {
@@ -105,16 +93,8 @@ export class thunderStastsCore {
let yesterdayMidnight = new Date();
yesterdayMidnight.setDate(yesterdayMidnight.getDate() - 1);
yesterdayMidnight.setHours(0, 0, 0, 0);
- let lastMidnight = new Date();
- lastMidnight.setHours(0, 0, 0, 0);
- //let lastMidnight = new Date(Date.now() - 56 * (24 * 60 * 60 * 1000)); // FOR TESTING ONLY
-
- // console.log(">>>>>>>>>>>>>> getYesterday yesterdayMidnight: " + JSON.stringify(yesterdayMidnight));
- // console.log(">>>>>>>>>>>>>> getYesterday lastMidnight: " + JSON.stringify(lastMidnight));
- let filter_duplicates = await tsCoreUtils.getFilterDuplicatesPreference(account_id);
-
- return this.getFullStatsData(yesterdayMidnight, lastMidnight, account_id, account_emails, false, filter_duplicates); // the "false" is to not aggregate, we will aggregate in the TAB_ManyDays.vue to exclude today
+ return this.getSingleDay(yesterdayMidnight, account_id, account_emails);
}
// ================ YESTERDAY TAB - END =====================
@@ -137,19 +117,54 @@ export class thunderStastsCore {
// ================ MANY DAYS TAB - END =====================
// ================ CUSTOM QUERY TAB =====================
- async getCustomQryData(fromDate, toDate, account_id = 0, account_emails = []) {
+ async getCustomQryData(fromDate, toDate, account_id = 0, account_emails = [], only_businessdays = -99) {
fromDate.setHours(0, 0, 0, 0);
toDate.setHours(23, 59, 59, 999);
let filter_duplicates = await tsCoreUtils.getFilterDuplicatesPreference(account_id);
- return this.getFullStatsData(fromDate, toDate, account_id, account_emails, true, filter_duplicates); // the "true" is to aggregate
+ return this.getFullStatsData(fromDate, toDate, account_id, account_emails, true, filter_duplicates, only_businessdays); // the "true" is to aggregate
}
// ================ CUSTOM QUERY TAB - END =====================
+ // ================ SINGLE DAY METHODS =====================
+ async getSingleDay(theDay, account_id = 0, account_emails = []) {
+
+ theDay.setHours(0, 0, 0, 0);
+ let theMidnightAfter = new Date(theDay);
+ theMidnightAfter.setDate(theMidnightAfter.getDate() + 1);
+ theMidnightAfter.setHours(0, 0, 0, 0);
+ //let lastMidnight = new Date(Date.now() - 56 * (24 * 60 * 60 * 1000)); // FOR TESTING ONLY
+
+ // console.log(">>>>>>>>>>>>>> getYesterday yesterdayMidnight: " + JSON.stringify(yesterdayMidnight));
+ // console.log(">>>>>>>>>>>>>> getYesterday lastMidnight: " + JSON.stringify(lastMidnight));
+
+ let filter_duplicates = await tsCoreUtils.getFilterDuplicatesPreference(account_id);
+
+ return this.getFullStatsData(theDay, theMidnightAfter, account_id, account_emails, false, filter_duplicates); // the "false" is to not aggregate, we will aggregate in the TAB_ManyDays.vue to exclude today
+ }
+
+ async getToday_SingleDayData(theDay, account_id = 0, account_emails = [], count_data_to_current_time = true) {
+
+ theDay.setHours(0, 0, 0, 0);
+ let theMidnightAfter = new Date(theDay);
+ theMidnightAfter.setDate(theMidnightAfter.getDate() + 1);
+ theMidnightAfter.setHours(0, 0, 0, 0);
+
+ let fromDate = new Date(theDay);
+ let toDate = new Date(theMidnightAfter);
+ // let fromDate = new Date(Date.now() - 56 * (24 * 60 * 60 * 1000)); // FOR TESTING ONLY
+ // let toDate = new Date(Date.now() - (24 * 60 * 60 * 1000)) // FOR TESTING ONLY
+
+ let filter_duplicates = await tsCoreUtils.getFilterDuplicatesPreference(account_id);
+
+ return this.getCountStatsData(fromDate, toDate, account_id, account_emails, count_data_to_current_time, filter_duplicates);
+ }
+ // ================ SINGLE DAY METHODS - END =====================
+
// ================ BASE METHODS ========================
- async getFullStatsData(fromDate, toDate, account_id = 0, account_emails = [], do_aggregate_stats = false, filter_duplicates = false) {
+ async getFullStatsData(fromDate, toDate, account_id = 0, account_emails = [], do_aggregate_stats = false, filter_duplicates = false, only_businessdays = -99) {
let start_time = performance.now();
// console.log(">>>>>>>>>>>> [getFullStatsData] filter_duplicates: " + filter_duplicates);
@@ -255,6 +270,9 @@ export class thunderStastsCore {
recipients[key_recipient].count = 1;
recipients[key_recipient].name = await tsCoreUtils.getCorrespondantName(recipient);
}
+ // console.log(">>>>>>>>>>>>>> message.headerMessageId: " + message.headerMessageId);
+ // console.log(">>>>>>>> key_recipient: " + key_recipient);
+ // console.log(">>>>>>>> recipients[key_recipient].count: " + recipients[key_recipient].count);
}
}
}
@@ -271,6 +289,9 @@ export class thunderStastsCore {
recipients[key_cc].count = 1;
recipients[key_cc].name = await tsCoreUtils.getCorrespondantName(cc);
}
+ // console.log(">>>>>>>>>>>>>> message.headerMessageId: " + message.headerMessageId);
+ // console.log(">>>>>>>> key_cc: " + key_cc);
+ // console.log(">>>>>>>> recipients[key_cc].count: " + recipients[key_cc].count);
}
}
}
@@ -282,6 +303,9 @@ export class thunderStastsCore {
senders[key_author].count = 1;
senders[key_author].name = await tsCoreUtils.getCorrespondantName(message.author);
}
+ // console.log(">>>>>>>>>>>>>> message.headerMessageId: " + message.headerMessageId);
+ // console.log(">>>>>>>> key_author: " + key_author);
+ // console.log(">>>>>>>> senders[key_author].count: " + senders[key_author].count);
received++;
// group by folder
folders[message.folder.id].received++;
@@ -302,10 +326,13 @@ export class thunderStastsCore {
recipients = Object.fromEntries(Object.entries(recipients).sort((a, b) => b[1].count - a[1].count));
recipients = Object.fromEntries(Object.entries(recipients).slice(0,this._involved_num));
+ // console.log(">>>>>>>> final senders: " + JSON.stringify(senders));
+ // console.log(">>>>>>>> final recipients: " + JSON.stringify(recipients));
+
let output = {senders: senders, recipients: recipients, sent: sent, received: received, count: count, msg_hours: msg_hours, folders: folders, dates: dates };
if(do_aggregate_stats) {
- output.aggregate = this.aggregateData(dates, sent, received);
+ output.aggregate = await this.aggregateData(dates, only_businessdays);
}
let stop_time = performance.now();
@@ -453,7 +480,7 @@ export class thunderStastsCore {
let output = {sent: sent, received: received, count: count, msg_days: msg_days};
- output.aggregate = this.aggregateData(msg_days, sent, received);
+ output.aggregate = await this.aggregateData(msg_days);
let stop_time = performance.now();
@@ -462,33 +489,63 @@ export class thunderStastsCore {
return output;
}
- aggregateData(dates, sent, received) {
- // dates must be popolated with all the days, even with 0 value
- let num_days = Object.keys(dates).length;
+ async aggregateData(dates, only_businessdays = -99) {
+
+ let filtered_dates = {};
+ let prefs_bday_default_only = false;
+
+ if(only_businessdays == -99) {
+ prefs_bday_default_only = await tsPrefs.getPref(['bday_default_only']);
+ } else {
+ prefs_bday_default_only = only_businessdays;
+ }
+
+ // console.log(">>>>>>>>>>> bday_default_only: " + prefs_bday_default_only);
+ if(prefs_bday_default_only == true){
+ for (let day_message in dates) {
+ if (tsCoreUtils.checkBusinessDay(day_message)) {
+ filtered_dates[day_message] = dates[day_message];
+ // console.log(">>>>>>>>>>>>> added day_message: " + JSON.stringify(day_message));
+ }else{
+ // console.log(">>>>>>>>>>>>> filtered day_message: " + JSON.stringify(day_message));
+ }
+ }
+ // console.log(">>>>>>>>>>> filtered_dates: " + JSON.stringify(filtered_dates));
+ }else{
+ filtered_dates = dates;
+ }
+
+ let total_sent = 0;
+ let total_received = 0;
let max_sent = 0;
let min_sent = 0;
- //let avg_sent = parseFloat((sent / tsUtils.daysBetween(fromDate, toDate)).toFixed(2));
- let avg_sent = parseFloat((sent / num_days).toFixed(2));
let max_received = 0;
let min_received = 0;
- //let avg_received = parseFloat((received / tsUtils.daysBetween(fromDate, toDate)).toFixed(2));
- let avg_received = parseFloat((received / num_days).toFixed(2));
- for(let i in dates) {
- if(dates[i].sent > max_sent) {
- max_sent = dates[i].sent;
+ for(let i in filtered_dates) {
+ total_sent += filtered_dates[i].sent;
+ total_received += filtered_dates[i].received;
+
+ if(filtered_dates[i].sent > max_sent) {
+ max_sent = filtered_dates[i].sent;
}
- if(dates[i].sent < min_sent) {
- min_sent = dates[i].sent;
+ if(filtered_dates[i].sent < min_sent) {
+ min_sent = filtered_dates[i].sent;
}
- if(dates[i].received > max_received) {
- max_received = dates[i].received;
+ if(filtered_dates[i].received > max_received) {
+ max_received = filtered_dates[i].received;
}
- if(dates[i].received < min_received) {
- min_received = dates[i].received;
+ if(filtered_dates[i].received < min_received) {
+ min_received = filtered_dates[i].received;
}
}
- return {max_sent: max_sent, min_sent: min_sent, avg_sent: avg_sent, max_received: max_received, min_received: min_received, avg_received: avg_received};
+
+ // filtered_dates must be popolated with all the days, even with 0 value
+ let num_days = Object.keys(filtered_dates).length;
+ let avg_sent = parseFloat((total_sent / num_days).toFixed(2));
+ let avg_received = parseFloat((total_received / num_days).toFixed(2));
+
+ return {total_sent: total_sent, max_sent: max_sent, min_sent: min_sent, avg_sent: avg_sent, total_received: total_received, max_received: max_received, min_received: min_received, avg_received: avg_received};
}
excludeMessage(message, account_id = 0){ // Returns true if the message should be excluded from the stats
diff --git a/statslib/mzts-statscore.utils.js b/statslib/mzts-statscore.utils.js
index 75b47b4d..95ad950e 100644
--- a/statslib/mzts-statscore.utils.js
+++ b/statslib/mzts-statscore.utils.js
@@ -17,7 +17,8 @@
*/
import { tsUtils } from "./mzts-utils";
-import { TS_prefs } from "./mzts-options";
+import { tsPrefs } from "./mzts-options";
+import { tsStore } from "./mzts-store";
export const tsCoreUtils = {
@@ -103,6 +104,15 @@ export const tsCoreUtils = {
return result;
},
+ filterTodayNextHours(hours) {
+ // Get the current time
+ const now = new Date();
+ const currentHour = now.getHours();
+
+ // Iterate over the array and set null for hours after the current hour
+ return hours.map((value, index) => index > currentHour ? null : value);
+ },
+
// getManyDaysLabels(labels) {
// const daysOfWeek = ["WeekDay0", "WeekDay1", "WeekDay2", "WeekDay3", "WeekDay4", "WeekDay5", "WeekDay6"];
@@ -127,6 +137,15 @@ export const tsCoreUtils = {
// });
// },
+ getDaysLabelColor(label) {
+ let isBusinessDay = this.checkBusinessDay(label);
+
+ const bd_color = tsStore.darkmode ? "white" : "black";
+ const nbd_color = tsStore.darkmode ? "#C18F2A" : "#725419";
+
+ return (isBusinessDay ? bd_color : nbd_color);
+ },
+
getCustomQryLabel(label) {
const year = parseInt(label.slice(0, 4));
const month = parseInt(label.slice(4, 6));
@@ -164,12 +183,16 @@ export const tsCoreUtils = {
const formattedDate = dateFormatter.format(date);
// return dayOfWeek + "\n" + formattedDate + (tsUtils.isToday(date) ? "\n[" + browser.i18n.getMessage("Today") + "]" : "");
- return [dayOfWeek,formattedDate,(tsUtils.isToday(date) ? "\n[" + browser.i18n.getMessage("Today") + "]" : "")];
+ return [
+ dayOfWeek,
+ formattedDate,
+ (tsUtils.isToday(date) ? "\n[" + browser.i18n.getMessage("Today") + "]" : ""),
+ ];
},
getManyDaysBarColor(ctx, totalBars) {
const defaultColor = '#4682B4';
- const todayColor = '#5BB4FD';
+ const todayColor = tsStore.darkmode ? '#1f9c6a' : '#2bc285';
return ctx.dataIndex === totalBars - 1 ? todayColor : defaultColor;
},
@@ -335,7 +358,7 @@ export const tsCoreUtils = {
},
async getAccountCustomIdentities(account_id = 0) {
- let prefCustomIds = await TS_prefs.getPref("custom_identities");
+ let prefCustomIds = await tsPrefs.getPref("custom_identities");
// console.log(">>>>>>>>>>>>> getAccountCustomIdentities prefCustomIds: " + JSON.stringify(prefCustomIds));
if(account_id == 0){ return prefCustomIds; }
if(prefCustomIds.hasOwnProperty(account_id)){
@@ -349,7 +372,7 @@ export const tsCoreUtils = {
let output = [];
for(let account of accounts) {
let include_archive = true;
- let filter_duplicates = await this.getDefaultAccountFilterDuplicatesOption(account);
+ let filter_duplicates = await this.getDefaultAccountFilterDuplicatesOption(account.id);
if(accounts_adv_settings.length > 0) {
include_archive = accounts_adv_settings.find(element => element.id === account.id)?.include_archive ?? include_archive;
filter_duplicates = accounts_adv_settings.find(element => element.id === account.id)?.filter_duplicates ?? filter_duplicates;
@@ -365,17 +388,17 @@ export const tsCoreUtils = {
return output;
},
- async getDefaultAccountFilterDuplicatesOption(account){
- let account_emails = await this.getAccountEmails(account.id,true);
+ async getDefaultAccountFilterDuplicatesOption(account_id){
+ let account_emails = await this.getAccountEmails(account_id,true);
// console.log(">>>>>>>>>>>>> getDefaultAccountFilterDuplicatesOption account_emails: " + JSON.stringify(account_emails));
return account_emails.some(email => email.toLowerCase().endsWith("@gmail.com"));
},
async getIncludeArchivePreference(account_id) {
if(account_id == 0) {
- return await TS_prefs.getPref("include_archive_multi_account");
+ return await tsPrefs.getPref("include_archive_multi_account");
} else {
- let accounts_adv_settings = await TS_prefs.getPref("accounts_adv_settings");
+ let accounts_adv_settings = await tsPrefs.getPref("accounts_adv_settings");
// console.log(">>>>>>>>>>>> [getFilterDuplicatesPreference] accounts_adv_settings: " + JSON.stringify(accounts_adv_settings));
let element = null;
if(accounts_adv_settings.length > 0) {
@@ -387,24 +410,104 @@ export const tsCoreUtils = {
}
},
- async getFilterDuplicatesPreference(account_id) {
+ async getFilterDuplicatesPreference(account_id) {
if(account_id == 0) {
- return await TS_prefs.getPref("filter_duplicates_multi_account");
+ return await tsPrefs.getPref("filter_duplicates_multi_account");
} else {
- let accounts_adv_settings = await TS_prefs.getPref("accounts_adv_settings");
- // console.log(">>>>>>>>>>>> [getFilterDuplicatesPreference] accounts_adv_settings: " + JSON.stringify(accounts_adv_settings));
- let element = null;
- if(accounts_adv_settings.length > 0) {
- element = accounts_adv_settings.find(account => account.id == account_id);
- }
- // console.log(">>>>>>>>>>>> [getFilterDuplicatesPreference] element: " + JSON.stringify(element));
- if(!element) return false;
- return element.filter_duplicates || false;
+ let accounts_adv_settings = await tsPrefs.getPref("accounts_adv_settings");
+ //console.log(">>>>>>>>>>>> [getFilterDuplicatesPreference] accounts_adv_settings: " + JSON.stringify(accounts_adv_settings));
+ let element = null;
+ if(accounts_adv_settings.length > 0) {
+ element = accounts_adv_settings.find(account => account.id == account_id);
+ }
+ //console.log(">>>>>>>>>>>> [getFilterDuplicatesPreference] element: " + JSON.stringify(element));
+ let filter_duplicates_defaults = await this.getDefaultAccountFilterDuplicatesOption(account_id);
+ if(!element) return filter_duplicates_defaults;
+ return element.filter_duplicates ?? filter_duplicates_defaults;
}
- },
+ },
+
+ // This function finds the first previous business day before the given date
+ findPreviousBusinessDay(date) {
+ let previousDate = new Date(date); // Create a copy of the original date
+
+ // Loop until a business day is found
+ do {
+ previousDate.setDate(previousDate.getDate() - 1); // Move to the previous day
+ } while (!this.checkBusinessDay(tsUtils.dateToYYYYMMDD(previousDate)));
+
+ return previousDate;
+ },
+
+ checkBusinessDay(datestr) { //datestr is a string like YYYYMMDD
+ let date = tsUtils.parseYYYYMMDDToDate(datestr);
+ let date_weekday = date.getDay();
+
+ // console.log(">>>>>>>>>>>>>> checkBusinessDay: " + datestr);
+
+ // check weekeday preference
+ if(tsStore["bday_weekdays_" + date_weekday] == false) {
+ return false;
+ }
+
+ // check easter preference
+ if(tsStore.bday_easter == true) {
+ if(this.isEasterOrEasterMonday(date)) {
+ return false;
+ }
+ }
+
+
+ // check custom non-business days preference
+ let custom_nbd = tsStore.bday_custom_days;
+
+ if(custom_nbd && custom_nbd.length > 0) {
+ for (let element of custom_nbd) {
+ let nbd_datestr = String(element.year == 'every_year' ? date.getFullYear() : element.year) +
+ String(element.month + 1).padStart(2, '0') +
+ String(element.day).padStart(2, '0');
+ // console.log(">>>>>>>>>>>>>> nbd_datestr: " + nbd_datestr);
+ // console.log(">>>>>>>>>>>>>> datestr: " + datestr);
+ if (nbd_datestr == datestr) {
+ // console.log(">>>>>>>>>>>>>> nbd_datestr == datestr: " + nbd_datestr + " == " + datestr);
+ return false;
+ }
+ }
+ }
+
+ // console.log(">>>>>>>>>>>>>> checkBusinessDay ["+datestr+"]: return true");
+ return true;
+ },
+
+ isEasterOrEasterMonday(date) {
+ const year = date.getFullYear();
+ const easterDate = this.calculateEaster(year);
+
+ // Calculate Easter Monday
+ const easterMonday = new Date(easterDate);
+ easterMonday.setDate(easterDate.getDate() + 1);
+
+ // Compare the provided date with Easter and Easter Monday
+ return (date.toDateString() === easterDate.toDateString()) ||
+ (date.toDateString() === easterMonday.toDateString());
+ },
+
+ calculateEaster(year) {
+ const f = Math.floor;
+ const G = year % 19;
+ const C = f(year / 100);
+ const H = (C - f(C / 4) - f((8 * C + 13) / 25) + 19 * G + 15) % 30;
+ const I = H - f(H / 28) * (1 - f(29 / (H + 1)) * f((21 - G) / 11));
+ const J = (year + f(year / 4) + I + 2 - C + f(C / 4)) % 7;
+ const L = I - J;
+ const month = 3 + f((L + 40) / 44); // March = 3, April = 4
+ const day = L + 28 - 31 * f(month / 4);
+ return new Date(year, month - 1, day);
+ },
}
+
const inboxZeroColors = [
"#1f77b4", "#aec7e8", "#ff7f0e", "#ffbb78", "#2ca02c",
"#98df8a", "#ff9896", "#9467bd", "#c5b0d5",
diff --git a/statslib/mzts-store.js b/statslib/mzts-store.js
index 417a1d27..dbf62352 100644
--- a/statslib/mzts-store.js
+++ b/statslib/mzts-store.js
@@ -3,4 +3,14 @@ import { reactive } from "vue"
export const tsStore = reactive({
'do_debug': false,
'darkmode': false,
+ 'businessdays_only': false,
+ 'bday_easter': false,
+ 'bday_custom_days': [],
+ 'bday_weekdays_0': false, //Sunday
+ 'bday_weekdays_1': true, //Monday
+ 'bday_weekdays_2': true, //Tuesday
+ 'bday_weekdays_3': true, //Wednesday
+ 'bday_weekdays_4': true, //Thursday
+ 'bday_weekdays_5': true, //Friday
+ 'bday_weekdays_6': false, //Saturday
});
\ No newline at end of file