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 @@
|
||||
* Duty list (timeline) rendering.
|
||||
*/
|
||||
|
||||
import { dutyListEl, state } from "./dom.js";
|
||||
import { getDutyListEl, state } from "./dom.js";
|
||||
import { t } from "./i18n.js";
|
||||
import { escapeHtml } from "./utils.js";
|
||||
import { buildContactLinksHtml } from "./contactHtml.js";
|
||||
import {
|
||||
localDateString,
|
||||
firstDayOfMonth,
|
||||
@@ -14,37 +15,6 @@ import {
|
||||
formatDateKey
|
||||
} from "./dateUtils.js";
|
||||
|
||||
/**
|
||||
* Build HTML for contact links (phone, Telegram) for a duty. Returns empty string if none.
|
||||
* @param {'ru'|'en'} lang
|
||||
* @param {object} d - Duty with optional phone, username
|
||||
* @returns {string}
|
||||
*/
|
||||
function dutyCardContactHtml(lang, d) {
|
||||
const parts = [];
|
||||
if (d.phone && String(d.phone).trim()) {
|
||||
const p = String(d.phone).trim();
|
||||
const safeHref = "tel:" + p.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<");
|
||||
parts.push(
|
||||
'<a href="' + safeHref + '" class="duty-contact-link duty-contact-phone">' +
|
||||
escapeHtml(p) + "</a>"
|
||||
);
|
||||
}
|
||||
if (d.username && String(d.username).trim()) {
|
||||
const u = String(d.username).trim().replace(/^@+/, "");
|
||||
if (u) {
|
||||
const href = "https://t.me/" + encodeURIComponent(u);
|
||||
parts.push(
|
||||
'<a href="' + href.replace(/"/g, """) + '" class="duty-contact-link duty-contact-username" target="_blank" rel="noopener noreferrer">@' +
|
||||
escapeHtml(u) + "</a>"
|
||||
);
|
||||
}
|
||||
}
|
||||
return parts.length
|
||||
? '<div class="duty-contact-row">' + parts.join(" · ") + "</div>"
|
||||
: "";
|
||||
}
|
||||
|
||||
/** Phone icon SVG for flip button (show contacts). */
|
||||
const ICON_PHONE =
|
||||
'<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z"/></svg>';
|
||||
@@ -79,7 +49,11 @@ export function dutyTimelineCardHtml(d, isCurrent) {
|
||||
? t(lang, "duty.now_on_duty")
|
||||
: (t(lang, "event_type." + (d.event_type || "duty")));
|
||||
const extraClass = isCurrent ? " duty-item--current" : "";
|
||||
const contactHtml = dutyCardContactHtml(lang, d);
|
||||
const contactHtml = buildContactLinksHtml(lang, d.phone, d.username, {
|
||||
classPrefix: "duty-contact",
|
||||
showLabels: false,
|
||||
separator: " · "
|
||||
});
|
||||
const hasContacts = Boolean(
|
||||
(d.phone && String(d.phone).trim()) ||
|
||||
(d.username && String(d.username).trim())
|
||||
@@ -174,12 +148,12 @@ export function dutyItemHtml(d, typeLabelOverride, showUntilEnd, extraClass) {
|
||||
'</span> <span class="name">' +
|
||||
escapeHtml(d.full_name) +
|
||||
'</span><div class="time">' +
|
||||
timeOrRange +
|
||||
escapeHtml(timeOrRange) +
|
||||
"</div></div>"
|
||||
);
|
||||
}
|
||||
|
||||
/** Whether the delegated flip-button click listener has been attached to dutyListEl. */
|
||||
/** Whether the delegated flip-button click listener has been attached to duty list element. */
|
||||
let flipListenerAttached = false;
|
||||
|
||||
/**
|
||||
@@ -187,6 +161,7 @@ let flipListenerAttached = false;
|
||||
* @param {object[]} duties - Duties (only duty type used for timeline)
|
||||
*/
|
||||
export function renderDutyList(duties) {
|
||||
const dutyListEl = getDutyListEl();
|
||||
if (!dutyListEl) return;
|
||||
|
||||
if (!flipListenerAttached) {
|
||||
@@ -277,8 +252,9 @@ export function renderDutyList(duties) {
|
||||
el.scrollIntoView({ behavior: "smooth", block: "start" });
|
||||
}
|
||||
};
|
||||
const currentDutyCard = dutyListEl.querySelector(".duty-item--current");
|
||||
const todayBlock = dutyListEl.querySelector(".duty-timeline-day--today");
|
||||
const listEl = getDutyListEl();
|
||||
const currentDutyCard = listEl ? listEl.querySelector(".duty-item--current") : null;
|
||||
const todayBlock = listEl ? listEl.querySelector(".duty-timeline-day--today") : null;
|
||||
if (currentDutyCard) {
|
||||
scrollToEl(currentDutyCard);
|
||||
} else if (todayBlock) {
|
||||
|
||||
Reference in New Issue
Block a user