feat: enhance CI workflow and update webapp styles
Some checks failed
CI / lint-and-test (push) Failing after 45s
Some checks failed
CI / lint-and-test (push) Failing after 45s
- Added Node.js setup and webapp testing steps to the CI workflow for improved integration. - Updated HTML to link multiple CSS files for better modularity and organization of styles. - Removed deprecated `style.css` and introduced new CSS files for base styles, calendar, day detail, hints, markers, states, and duty list to enhance maintainability and readability. - Implemented new styles for improved presentation of duty information and user interactions. - Added unit tests for new API functions and contact link rendering to ensure functionality and reliability.
This commit is contained in:
@@ -2,9 +2,10 @@
|
||||
* Day detail panel: popover (desktop) or bottom sheet (mobile) on calendar cell tap.
|
||||
*/
|
||||
|
||||
import { calendarEl, state } from "./dom.js";
|
||||
import { getCalendarEl, state } from "./dom.js";
|
||||
import { t } from "./i18n.js";
|
||||
import { escapeHtml } from "./utils.js";
|
||||
import { buildContactLinksHtml } from "./contactHtml.js";
|
||||
import { localDateString, dateKeyToDDMM } from "./dateUtils.js";
|
||||
import { getDutyMarkerRows } from "./hints.js";
|
||||
|
||||
@@ -35,43 +36,6 @@ function parseDataAttr(raw) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build HTML for contact info (phone link, Telegram username link) for a duty entry.
|
||||
* @param {'ru'|'en'} lang
|
||||
* @param {string|null|undefined} phone
|
||||
* @param {string|null|undefined} username - Telegram username with or without leading @
|
||||
* @returns {string}
|
||||
*/
|
||||
function buildContactHtml(lang, phone, username) {
|
||||
const parts = [];
|
||||
if (phone && String(phone).trim()) {
|
||||
const p = String(phone).trim();
|
||||
const label = t(lang, "contact.phone");
|
||||
const safeHref = "tel:" + p.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<");
|
||||
parts.push(
|
||||
'<span class="day-detail-contact">' +
|
||||
escapeHtml(label) + ": " +
|
||||
'<a href="' + safeHref + '" class="day-detail-contact-link day-detail-contact-phone">' +
|
||||
escapeHtml(p) + "</a></span>"
|
||||
);
|
||||
}
|
||||
if (username && String(username).trim()) {
|
||||
const u = String(username).trim().replace(/^@+/, "");
|
||||
if (u) {
|
||||
const label = t(lang, "contact.telegram");
|
||||
const display = "@" + u;
|
||||
const href = "https://t.me/" + encodeURIComponent(u);
|
||||
parts.push(
|
||||
'<span class="day-detail-contact">' +
|
||||
escapeHtml(label) + ": " +
|
||||
'<a href="' + escapeHtml(href) + '" class="day-detail-contact-link day-detail-contact-username" target="_blank" rel="noopener noreferrer">' +
|
||||
escapeHtml(display) + "</a></span>"
|
||||
);
|
||||
}
|
||||
}
|
||||
return parts.length ? '<div class="day-detail-contact-row">' + parts.join(" ") + "</div>" : "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Build HTML content for the day detail panel.
|
||||
* @param {string} dateKey - YYYY-MM-DD
|
||||
@@ -127,7 +91,11 @@ export function buildDayDetailContent(dateKey, duties, eventSummaries) {
|
||||
const phone = r.phone != null ? r.phone : (duty && duty.phone);
|
||||
const username = r.username != null ? r.username : (duty && duty.username);
|
||||
const timeHtml = r.timePrefix ? escapeHtml(r.timePrefix) + " — " : "";
|
||||
const contactHtml = buildContactHtml(lang, phone, username);
|
||||
const contactHtml = buildContactLinksHtml(lang, phone, username, {
|
||||
classPrefix: "day-detail-contact",
|
||||
showLabels: true,
|
||||
separator: " "
|
||||
});
|
||||
html +=
|
||||
"<li>" +
|
||||
(timeHtml ? '<span class="day-detail-time">' + timeHtml + "</span>" : "") +
|
||||
@@ -199,6 +167,7 @@ function positionPopover(panel, cellRect) {
|
||||
const panelRect = panel.getBoundingClientRect();
|
||||
let left = cellRect.left + cellRect.width / 2 - panelRect.width / 2;
|
||||
let top = cellRect.bottom + 8;
|
||||
/* day-detail-panel--below: panel is positioned above the cell (not enough space below). Used for optional styling (e.g. arrow). */
|
||||
if (top + panelRect.height > vh - margin) {
|
||||
top = cellRect.top - panelRect.height - 8;
|
||||
panel.classList.add("day-detail-panel--below");
|
||||
@@ -256,6 +225,7 @@ function showAsPopover(cellRect) {
|
||||
const target = e.target instanceof Node ? e.target : null;
|
||||
if (!target || !panelEl) return;
|
||||
if (panelEl.contains(target)) return;
|
||||
const calendarEl = getCalendarEl();
|
||||
if (target instanceof HTMLElement && calendarEl && calendarEl.contains(target) && target.closest(".day")) return;
|
||||
hideDayDetail();
|
||||
};
|
||||
@@ -390,6 +360,7 @@ function ensurePanelInDom() {
|
||||
* Bind delegated click/keydown on calendar for .day cells.
|
||||
*/
|
||||
export function initDayDetail() {
|
||||
const calendarEl = getCalendarEl();
|
||||
if (!calendarEl) return;
|
||||
calendarEl.addEventListener("click", (e) => {
|
||||
const cell = /** @type {HTMLElement} */ (e.target instanceof HTMLElement ? e.target.closest(".day") : null);
|
||||
|
||||
Reference in New Issue
Block a user