adminforth 3.27.1 → 3.28.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -51,6 +51,7 @@ import dayjs from 'dayjs';
51
51
  import utc from 'dayjs/plugin/utc';
52
52
 
53
53
  import {useCoreStore} from '@/stores/core';
54
+ import {FLEXIBLE_DATEPICKER_FORMAT} from '@/utils';
54
55
 
55
56
  import IconCalendar from "@/components/icons/IconCalendar.vue";
56
57
  import IconTime from "@/components/icons/IconTime.vue";
@@ -138,7 +139,7 @@ watch(start, () => {
138
139
 
139
140
  async function initDatepickers() {
140
141
  const LS_LANG_KEY = `afLanguage`;
141
- const options = {format: 'dd M yyyy', language: localStorage.getItem(LS_LANG_KEY)};
142
+ const options = {format: FLEXIBLE_DATEPICKER_FORMAT, language: localStorage.getItem(LS_LANG_KEY)};
142
143
 
143
144
  if (props.autoHide) {
144
145
  options.autohide = true;
@@ -69,6 +69,7 @@ import dayjs from 'dayjs';
69
69
  import utc from 'dayjs/plugin/utc';
70
70
 
71
71
  import {useCoreStore} from '@/stores/core';
72
+ import {FLEXIBLE_DATEPICKER_FORMAT} from '@/utils';
72
73
 
73
74
  import IconCalendar from "@/components/icons/IconCalendar.vue";
74
75
  import IconTime from "@/components/icons/IconTime.vue";
@@ -213,8 +214,9 @@ async function initDatepickers() {
213
214
  }
214
215
 
215
216
  const LS_LANG_KEY = `afLanguage`;
216
- datepickerStartObject.value = new Datepicker(datepickerStartEl.value, {format: 'dd M yyyy', language: localStorage.getItem(LS_LANG_KEY)});
217
- datepickerEndObject.value = new Datepicker(datepickerEndEl.value, {format: 'dd M yyyy', language: localStorage.getItem(LS_LANG_KEY)});
217
+ const options = {format: FLEXIBLE_DATEPICKER_FORMAT, language: localStorage.getItem(LS_LANG_KEY)};
218
+ datepickerStartObject.value = new Datepicker(datepickerStartEl.value, options);
219
+ datepickerEndObject.value = new Datepicker(datepickerEndEl.value, options);
218
220
  addChangeDateListener();
219
221
  }
220
222
 
@@ -49,3 +49,109 @@ export function formatTime(time: string | null | undefined): string {
49
49
  if (!time) return '';
50
50
  return dayjs(`0000-00-00 ${time}`).format(getTimeFormat());
51
51
  }
52
+
53
+ /** Locale shape flowbite-datepicker passes to custom format functions. */
54
+ type DatepickerLocale = { months: string[], monthsShort: string[] };
55
+
56
+ /** A run of letters or a run of digits, so "18Aug2003" and "Jun 23, 2022 ·" both tokenize cleanly. */
57
+ const DATE_TOKEN_RE = /\p{L}+|\p{N}+/gu;
58
+ /** Time (and timezone offset) part of a pasted datetime, dropped before tokenizing the date. */
59
+ const TIME_PART_RE = /\d{1,2}:\d{2}(:\d{2})?(\.\d+)?\s*([ap]\.?m\.?)?/gi;
60
+ /** A token consisting of digits only. */
61
+ const DIGITS_RE = /^\p{N}+$/u;
62
+
63
+ const EN_MONTHS = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
64
+ const EN_MONTHS_SHORT = EN_MONTHS.map(m => m.slice(0, 3));
65
+
66
+ /** Two-digit years below this belong to the 2000s, the rest to the 1900s. */
67
+ const CENTURY_PIVOT = 50;
68
+ /** Shortest month abbreviation across locales; single letters would match random words. */
69
+ const MIN_MONTH_NAME_LENGTH = 2;
70
+
71
+ function findMonthIndex(name: string, locale: DatepickerLocale): number {
72
+ if (name.length < MIN_MONTH_NAME_LENGTH) {
73
+ return -1;
74
+ }
75
+ const lowered = name.toLowerCase();
76
+ const startsWithToken = (monthName: string) => monthName.toLowerCase().startsWith(lowered);
77
+ // both short and full names, in the active language and in English (pasted dates are often English)
78
+ for (const names of [locale.monthsShort, locale.months, EN_MONTHS_SHORT, EN_MONTHS]) {
79
+ const index = names.findIndex(startsWithToken);
80
+ if (index >= 0) {
81
+ return index;
82
+ }
83
+ }
84
+ return -1;
85
+ }
86
+
87
+ function normalizeYear(year: number, digits: number): number {
88
+ if (digits > 2) {
89
+ return year;
90
+ }
91
+ return year < CENTURY_PIVOT ? 2000 + year : 1900 + year;
92
+ }
93
+
94
+ /**
95
+ * Parses a human-typed or pasted date in whatever order the parts came in:
96
+ * "18 Aug 2003", "Aug 18, 2003", "18.08.2003", "2003-08-18", "Jun 23, 2022 ·".
97
+ * Returns a local-midnight timestamp, or NaN when the string is not a date.
98
+ */
99
+ export function parseFlexibleDate(dateStr: string, locale: DatepickerLocale): number {
100
+ const tokens = dateStr.replace(TIME_PART_RE, ' ').match(DATE_TOKEN_RE) || [];
101
+
102
+ let month = -1;
103
+ const numbers: { value: number, digits: number }[] = [];
104
+ for (const token of tokens) {
105
+ if (DIGITS_RE.test(token)) {
106
+ numbers.push({ value: parseInt(token, 10), digits: token.length });
107
+ } else if (month < 0) {
108
+ month = findMonthIndex(token, locale);
109
+ }
110
+ }
111
+
112
+ const yearIndex = numbers.findIndex(n => n.digits > 2);
113
+ const yearToken = yearIndex >= 0 ? numbers.splice(yearIndex, 1)[0] : undefined;
114
+
115
+ let day: number;
116
+ if (month >= 0) {
117
+ day = numbers.length ? numbers.shift()!.value : 1;
118
+ } else if (numbers.length >= 2) {
119
+ // year first means ISO-like order, otherwise day first (the format we render)
120
+ const [first, second] = yearIndex === 0 ? [numbers[1], numbers[0]] : numbers;
121
+ day = first.value;
122
+ month = second.value - 1;
123
+ numbers.splice(0, 2);
124
+ if (month > 11 && day <= 12) {
125
+ [day, month] = [month + 1, day - 1];
126
+ }
127
+ } else if (numbers.length === 1) {
128
+ day = numbers.shift()!.value;
129
+ month = new Date().getMonth();
130
+ } else {
131
+ return NaN;
132
+ }
133
+
134
+ const leftover = numbers.shift();
135
+ const year = yearToken
136
+ ? normalizeYear(yearToken.value, yearToken.digits)
137
+ : leftover
138
+ ? normalizeYear(leftover.value, leftover.digits)
139
+ : new Date().getFullYear();
140
+
141
+ const date = new Date(year, month, day);
142
+ date.setFullYear(year);
143
+ // rejects impossible dates like "31 Feb" instead of silently rolling them over
144
+ return date.getMonth() === month && date.getDate() === day ? date.getTime() : NaN;
145
+ }
146
+
147
+ /**
148
+ * flowbite-datepicker format: renders as "18 Aug 2003" and accepts any parsable order on input.
149
+ */
150
+ export const FLEXIBLE_DATEPICKER_FORMAT = {
151
+ toDisplay(date: Date, format: unknown, locale: DatepickerLocale): string {
152
+ return `${String(date.getDate()).padStart(2, '0')} ${locale.monthsShort[date.getMonth()]} ${date.getFullYear()}`;
153
+ },
154
+ toValue(dateStr: string, format: unknown, locale: DatepickerLocale): number {
155
+ return parseFlexibleDate(dateStr, locale);
156
+ },
157
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "adminforth",
3
- "version": "3.27.1",
3
+ "version": "3.28.0",
4
4
  "description": "OpenSource Agent-Native forth-generation admin panel",
5
5
  "keywords": [
6
6
  "adminforth",