Implement phone number normalization and access control for Telegram users
- Added functionality to normalize phone numbers for comparison, ensuring only digits are stored and checked. - Updated configuration to include optional phone number allowlists for users and admins in the environment settings. - Enhanced authentication logic to allow access based on normalized phone numbers, in addition to usernames. - Introduced new helper functions for parsing and validating phone numbers, improving code organization and maintainability. - Added unit tests to validate phone normalization and access control based on phone numbers.
This commit is contained in:
@@ -295,13 +295,13 @@
|
||||
let html = "<span class=\"num\">" + d.getDate() + "</span><div class=\"day-markers\">";
|
||||
if (showMarkers) {
|
||||
if (dutyList.length) {
|
||||
html += "<span class=\"duty-marker\" data-names=\"" + namesAttr(dutyList) + "\" title=\"" + titleAttr(dutyList) + "\" aria-label=\"Дежурные\">Д</span>";
|
||||
html += "<button type=\"button\" class=\"duty-marker\" data-event-type=\"duty\" data-names=\"" + namesAttr(dutyList) + "\" title=\"" + titleAttr(dutyList) + "\" aria-label=\"Дежурные\">Д</button>";
|
||||
}
|
||||
if (unavailableList.length) {
|
||||
html += "<span class=\"unavailable-marker\" data-names=\"" + namesAttr(unavailableList) + "\" title=\"" + titleAttr(unavailableList) + "\" aria-label=\"Недоступен\">Н</span>";
|
||||
html += "<button type=\"button\" class=\"unavailable-marker\" data-event-type=\"unavailable\" data-names=\"" + namesAttr(unavailableList) + "\" title=\"" + titleAttr(unavailableList) + "\" aria-label=\"Недоступен\">Н</button>";
|
||||
}
|
||||
if (vacationList.length) {
|
||||
html += "<span class=\"vacation-marker\" data-names=\"" + namesAttr(vacationList) + "\" title=\"" + titleAttr(vacationList) + "\" aria-label=\"Отпуск\">О</span>";
|
||||
html += "<button type=\"button\" class=\"vacation-marker\" data-event-type=\"vacation\" data-names=\"" + namesAttr(vacationList) + "\" title=\"" + titleAttr(vacationList) + "\" aria-label=\"Отпуск\">О</button>";
|
||||
}
|
||||
if (hasEvent) {
|
||||
html += "<button type=\"button\" class=\"info-btn\" aria-label=\"Информация о дне\" data-summary=\"" + escapeHtml(eventSummaries.join("\n")) + "\">i</button>";
|
||||
@@ -372,8 +372,9 @@
|
||||
btn.addEventListener("click", function (e) {
|
||||
e.stopPropagation();
|
||||
var summary = btn.getAttribute("data-summary") || "";
|
||||
if (hintEl.hidden || hintEl.textContent !== summary) {
|
||||
hintEl.textContent = summary;
|
||||
var content = "События:\n" + summary;
|
||||
if (hintEl.hidden || hintEl.textContent !== content) {
|
||||
hintEl.textContent = content;
|
||||
var rect = btn.getBoundingClientRect();
|
||||
positionHint(hintEl, rect);
|
||||
hintEl.dataset.active = "1";
|
||||
@@ -400,6 +401,20 @@
|
||||
}
|
||||
}
|
||||
|
||||
var EVENT_TYPE_LABELS = { duty: "Дежурство", unavailable: "Недоступен", vacation: "Отпуск" };
|
||||
|
||||
function getDutyMarkerHintContent(marker) {
|
||||
var type = marker.getAttribute("data-event-type") || "duty";
|
||||
var label = EVENT_TYPE_LABELS[type] || type;
|
||||
var names = (marker.getAttribute("data-names") || "").replace(/\n/g, ", ");
|
||||
return names ? label + ": " + names : label;
|
||||
}
|
||||
|
||||
function clearActiveDutyMarker() {
|
||||
calendarEl.querySelectorAll(".duty-marker.calendar-marker-active, .unavailable-marker.calendar-marker-active, .vacation-marker.calendar-marker-active")
|
||||
.forEach(function (m) { m.classList.remove("calendar-marker-active"); });
|
||||
}
|
||||
|
||||
function bindDutyMarkerTooltips() {
|
||||
var hintEl = document.getElementById("dutyMarkerHint");
|
||||
if (!hintEl) {
|
||||
@@ -418,23 +433,54 @@
|
||||
clearTimeout(hideTimeout);
|
||||
hideTimeout = null;
|
||||
}
|
||||
var names = marker.getAttribute("data-names") || "";
|
||||
hintEl.textContent = names;
|
||||
hintEl.textContent = getDutyMarkerHintContent(marker);
|
||||
var rect = marker.getBoundingClientRect();
|
||||
positionHint(hintEl, rect);
|
||||
hintEl.hidden = false;
|
||||
});
|
||||
marker.addEventListener("mouseleave", function () {
|
||||
if (hintEl.dataset.active) { return; }
|
||||
hideTimeout = setTimeout(function () {
|
||||
hintEl.hidden = true;
|
||||
hideTimeout = null;
|
||||
}, 150);
|
||||
});
|
||||
marker.addEventListener("click", function (e) {
|
||||
e.stopPropagation();
|
||||
if (marker.classList.contains("calendar-marker-active")) {
|
||||
hintEl.hidden = true;
|
||||
hintEl.removeAttribute("data-active");
|
||||
marker.classList.remove("calendar-marker-active");
|
||||
return;
|
||||
}
|
||||
clearActiveDutyMarker();
|
||||
hintEl.textContent = getDutyMarkerHintContent(marker);
|
||||
var rect = marker.getBoundingClientRect();
|
||||
positionHint(hintEl, rect);
|
||||
hintEl.hidden = false;
|
||||
hintEl.dataset.active = "1";
|
||||
marker.classList.add("calendar-marker-active");
|
||||
});
|
||||
});
|
||||
if (!document._dutyMarkerHintBound) {
|
||||
document._dutyMarkerHintBound = true;
|
||||
document.addEventListener("click", function () {
|
||||
if (hintEl.dataset.active) {
|
||||
hintEl.hidden = true;
|
||||
hintEl.removeAttribute("data-active");
|
||||
clearActiveDutyMarker();
|
||||
}
|
||||
});
|
||||
document.addEventListener("keydown", function (e) {
|
||||
if (e.key === "Escape" && hintEl.dataset.active) {
|
||||
hintEl.hidden = true;
|
||||
hintEl.removeAttribute("data-active");
|
||||
clearActiveDutyMarker();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
var EVENT_TYPE_LABELS = { duty: "Дежурство", unavailable: "Недоступен", vacation: "Отпуск" };
|
||||
|
||||
/** Format UTC date from ISO string as DD.MM for display. */
|
||||
/** Format date as DD.MM in user's local timezone (for duty card labels). */
|
||||
function formatDateKey(isoDateStr) {
|
||||
@@ -540,7 +586,17 @@
|
||||
dutyListEl.innerHTML = fullHtml;
|
||||
var scrollTarget = dutyListEl.querySelector(".duty-timeline-day--today");
|
||||
if (scrollTarget) {
|
||||
scrollTarget.scrollIntoView({ behavior: "auto", block: "start" });
|
||||
var calendarSticky = document.getElementById("calendarSticky");
|
||||
if (calendarSticky) {
|
||||
requestAnimationFrame(function () {
|
||||
var calendarHeight = calendarSticky.offsetHeight;
|
||||
var todayTop = scrollTarget.getBoundingClientRect().top + window.scrollY;
|
||||
var scrollTop = Math.max(0, todayTop - calendarHeight);
|
||||
window.scrollTo({ top: scrollTop, behavior: "auto" });
|
||||
});
|
||||
} else {
|
||||
scrollTarget.scrollIntoView({ behavior: "auto", block: "start" });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user