Skip to content

Commit a532c6c

Browse files
committed
implement bootstrap tooltips
1 parent e0fde61 commit a532c6c

File tree

8 files changed

+206
-44
lines changed

8 files changed

+206
-44
lines changed

admin.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@
137137
if (curl_errno($ch)) {
138138
die('Could not fetch data from ' . $eventsEndpoint);
139139
}
140-
[ "litcal_events" => $LitCalAllFestivities ] = json_decode(
140+
[ "litcal_events" => $LitCalAllLitEvents ] = json_decode(
141141
$eventsRaw,
142142
true
143143
);
@@ -182,7 +182,7 @@ class="bi bi-exclamation-triangle-fill flex-shrink-0 me-2" viewBox="0 0 16 16"
182182
</div>
183183
<?php
184184
$testsIndex = json_encode($LitCalTests);
185-
$litcal_events = json_encode($LitCalAllFestivities);
185+
$litcal_events = json_encode($LitCalAllLitEvents);
186186
echo "<script>const LitCalTests = Object.freeze($testsIndex); const LitcalEvents = Object.freeze($litcal_events);</script>";
187187
include_once 'layout/footer.php';
188188
?>

assets/css/index.css

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,21 @@ body {
2727
width: 4rem;
2828
height: 4rem;
2929
}
30+
31+
.wide-tooltip .tooltip-inner {
32+
max-width: 800px;
33+
white-space: pre;
34+
text-align: left;
35+
font-size: 0.8rem;
36+
overflow-x: auto;
37+
}
38+
39+
.wide-tooltip .tooltip-inner a {
40+
color: #9ab8de;
41+
transition: color 300ms 100ms;
42+
}
43+
44+
.wide-tooltip .tooltip-inner a:hover {
45+
color: #e8d2d2;
46+
transition: color 300ms 100ms;
47+
}

assets/js/admin.js

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -259,7 +259,7 @@ const setEndpoints = (ev = null) => {
259259
case 'v3':
260260
ENDPOINTS.METADATA = `https://litcal.johnromanodorazio.com/api/v3/LitCalMetadata.php`;
261261
ENDPOINTS.TESTSINDEX = `https://litcal.johnromanodorazio.com/api/v3/LitCalTestsIndex.php`;
262-
ENDPOINTS.EVENTS = `https://litcal.johnromanodorazio.com/api/v4/LitCalAllFestivities.php/`;
262+
ENDPOINTS.EVENTS = `https://litcal.johnromanodorazio.com/api/v4/LitCalAllLitEvents.php/`;
263263
break;
264264
}
265265
}
@@ -477,15 +477,15 @@ const API = {
477477

478478
/**
479479
* Rebuilds the options for the select element containing the list of existing
480-
* festivities for the currently selected API calendar.
480+
* liturgical events for the currently selected API calendar.
481481
*
482482
* @param {HTMLSelectElement} element - The select element containing the list of
483-
* existing festivities.
483+
* existing liturgical events.
484484
* @async
485485
* @returns {Promise<void>}
486486
*/
487-
const rebuildFestivitiesOptions = async (element) => {
488-
console.log(`rebuildFestivitiesOptions: ${element.value}`);
487+
const rebuildLitEventsOptions = async (element) => {
488+
console.log(`rebuildLitEventsOptions: ${element.value}`);
489489
const selectedOption = $(element).find('option[value="' + element.value + '"]')[0];
490490
console.log(selectedOption);
491491
const calendarType = selectedOption.dataset.calendartype;
@@ -512,7 +512,7 @@ const rebuildFestivitiesOptions = async (element) => {
512512
}
513513
htmlStr += `<option value="${el.event_key}"${dataMonth}${dataDay}${dataGrade}>${el.name} (${el.grade_lcl})</option>`;
514514
}
515-
document.querySelector( '#existingFestivitiesList' ).innerHTML = htmlStr;
515+
document.querySelector( '#existingLitEventsList' ).innerHTML = htmlStr;
516516
}
517517

518518
/**
@@ -556,9 +556,9 @@ $(document).on('change', '#litCalTestsSelect', async (ev) => {
556556
if( proxiedTest.hasOwnProperty('applies_to') ) {
557557
const calendarType = Object.keys(proxiedTest.applies_to)[0];
558558
document.querySelector('#APICalendarSelect').value = proxiedTest.applies_to[calendarType];
559-
await rebuildFestivitiesOptions(document.querySelector('#APICalendarSelect'));
560-
document.querySelector('#existingFestivityName').value = proxiedTest.event_key;
561-
console.log(`keys of litcal_events after rebuildFestivitiesOptions:`);
559+
await rebuildLitEventsOptions(document.querySelector('#APICalendarSelect'));
560+
document.querySelector('#existingLitEventName').value = proxiedTest.event_key;
561+
console.log(`keys of litcal_events after rebuildLitEventsOptions:`);
562562
console.log(litcal_events_keys.sort());
563563
AssertionsBuilder.test = litcal_events.filter(el => el.event_key === proxiedTest.event_key)[0] ?? null;
564564
AssertionsBuilder.appliesTo = proxiedTest.applies_to[calendarType];
@@ -577,7 +577,7 @@ $(document).on('change', '#litCalTestsSelect', async (ev) => {
577577
});
578578

579579
$(document).on('change', '#APICalendarSelect', async (ev) => {
580-
await rebuildFestivitiesOptions( ev.currentTarget );
580+
await rebuildLitEventsOptions( ev.currentTarget );
581581
});
582582

583583
$(document).on('click', '.editDate', ev => {
@@ -756,8 +756,8 @@ $(document).on('show.bs.modal', '#modalDefineTest', ev => {
756756
let lightClass = '';
757757
if( 'edittest' in ev.relatedTarget.dataset ) {
758758
document.querySelector('#newUnitTestDescription').value = proxiedTest.description;
759-
document.querySelector('#existingFestivityName').value = proxiedTest.event_key;
760-
$existingOption = $(document.querySelector('#existingFestivityName').list).find('option[value="' + proxiedTest.event_key + '"]');
759+
document.querySelector('#existingLitEventName').value = proxiedTest.event_key;
760+
$existingOption = $(document.querySelector('#existingLitEventName').list).find('option[value="' + proxiedTest.event_key + '"]');
761761
console.log($existingOption);
762762
years = Array.from(document.querySelectorAll('#assertionsContainer .testYear')).map(el => Number(el.textContent));
763763
minYear = Math.min(...years);
@@ -824,8 +824,8 @@ $(document).on('slid.bs.carousel', ev => {
824824
if( ev.to === 2 ) {
825825
let firstYear = document.querySelector('#lowerRange').value;
826826
let monthDay = '-01-01';
827-
const selectedEventVal = document.querySelector('#existingFestivityName').value;
828-
const $selectedOption = $('#existingFestivitiesList').find('option[value="' + selectedEventVal + '"]');
827+
const selectedEventVal = document.querySelector('#existingLitEventName').value;
828+
const $selectedOption = $('#existingLitEventsList').find('option[value="' + selectedEventVal + '"]');
829829
if( $selectedOption.length && $selectedOption[0].dataset.month && $selectedOption[0].dataset.month !== '' && $selectedOption[0].dataset.day && $selectedOption[0].dataset.day !== '' ) {
830830
const month = $selectedOption[0].dataset.month.padStart(2, '0');
831831
const day = $selectedOption[0].dataset.day.padStart(2, '0');
@@ -839,7 +839,7 @@ $(document).on('slid.bs.carousel', ev => {
839839

840840
/** SLIDER 1 INTERACTIONS */
841841

842-
$(document).on('change', '#existingFestivityName', ev => {
842+
$(document).on('change', '#existingLitEventName', ev => {
843843
const currentVal = ev.currentTarget.value;
844844
console.log(currentVal);
845845
// Determine whether an option exists with the current value of the input.
@@ -895,8 +895,8 @@ $(document).on('change', '#yearsToTestRangeSlider [type=range]', ev => {
895895
let htmlStr = '';
896896
let titleAttr = '';
897897
let lightClass = '';
898-
const currentEventKey = document.querySelector('#existingFestivityName').value;
899-
$existingOption = $(document.querySelector('#existingFestivityName').list).find('option[value="' + currentEventKey + '"]');
898+
const currentEventKey = document.querySelector('#existingLitEventName').value;
899+
$existingOption = $(document.querySelector('#existingLitEventName').list).find('option[value="' + currentEventKey + '"]');
900900
for( let year = minYear; year <= maxYear; year++ ) {
901901
eventDate = new Date(Date.UTC(year, Number($existingOption[0].dataset.month)-1, Number($existingOption[0].dataset.day), 0, 0, 0));
902902
if( eventDate.getUTCDay() === 0 ) {
@@ -965,8 +965,8 @@ $(document).on('click', '#btnCreateTest', () => {
965965
} else {
966966
//let's build our new Unit Test
967967
proxiedTest = new Proxy({}, sanitizeOnSetValue);
968-
proxiedTest.event_key = document.querySelector('#existingFestivityName').value;
969-
console.log(document.querySelector('#existingFestivityName').value);
968+
proxiedTest.event_key = document.querySelector('#existingLitEventName').value;
969+
console.log(document.querySelector('#existingLitEventName').value);
970970
console.log(proxiedTest.name);
971971
proxiedTest.test_type = currentTestType;
972972
proxiedTest.description = document.querySelector('#newUnitTestDescription').value;

assets/js/index.js

Lines changed: 62 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -303,16 +303,16 @@ const sourceDataCheckTemplate = ( item, idx ) => {
303303
let categoryStr;
304304
switch(item.category){
305305
case 'nationalcalendar':
306-
categoryStr = 'National Calendar definition: defines any actions that need to be taken on the festivities already defined in the Universal Calendar, to adapt them to this specific National Calendar';
306+
categoryStr = 'National Calendar definition: defines any actions that need to be taken on the liturgical events already defined in the Universal Calendar, to adapt them to this specific National Calendar';
307307
break;
308308
case 'widerregioncalendar':
309-
categoryStr = 'Wider Region definition: contains any festivities that apply not only to a particular nation, but to a group of nations that belong to the wider region. There will also be translation files associated with this data';
309+
categoryStr = 'Wider Region definition: contains any liturgical events that apply not only to a particular nation, but to a group of nations that belong to the wider region. There will also be translation files associated with this data';
310310
break;
311311
case 'propriumdesanctis':
312-
categoryStr = 'Proprium de Sanctis data: contains any festivities defined in the Missal printed for the given nation, that are not already defined in the Universal Calendar';
312+
categoryStr = 'Proprium de Sanctis data: contains any liturgical events defined in the Missal printed for the given nation, that are not already defined in the Universal Calendar';
313313
break;
314314
case 'diocesancalendar':
315-
categoryStr = 'Diocesan Calendar definition: contains any festivities that are proper to the given diocese. This data will not overwrite national or universal calendar data, it will be simply appended to the calendar';
315+
categoryStr = 'Diocesan Calendar definition: contains any liturgical events that are proper to the given diocese. This data will not overwrite national or universal calendar data, it will be simply appended to the calendar';
316316
break;
317317
}
318318
return `<div class="col-1${idx === 0 || idx % 11 === 0 ? ' offset-1' : ''}">
@@ -363,6 +363,14 @@ const HTMLEncode = (str) => {
363363
return aRet.join('');
364364
}
365365

366+
const escapeQuotesAndLinkifyUrls = (str) => {
367+
str = str.replaceAll(
368+
/(https?:\/\/.+?)(?=\s|$)/g,
369+
(url) => `<a href="${url}" target="_blank">${url}</a>`
370+
);
371+
return str.replaceAll('"', '&quot;');
372+
}
373+
366374
/**
367375
* The options used to format the date in the assertions.
368376
* @constant
@@ -620,7 +628,7 @@ const connectWebSocket = () => {
620628
else if ( responseData.type === "error" ) {
621629
$( responseData.classes ).removeClass( 'bg-info' ).addClass( 'bg-danger' );
622630
$( responseData.classes ).find( '.fa-circle-question' ).removeClass( 'fa-circle-question' ).addClass( 'fa-circle-xmark' );
623-
$( responseData.classes ).find('.card-text').append(`<span title="${HTMLEncode( responseData.text )}" role="button" class="float-right"><i class="fas fa-message-exclamation"></i></span>`);
631+
$( responseData.classes ).find('.card-text').append(`<span role="button" class="float-end error-tooltip" data-bs-toggle="tooltip" data-bs-title="${escapeQuotesAndLinkifyUrls( responseData.text )}"><i class="fas fa-message-exclamation"></i></span>`);
624632
$( '#failedCount' ).text( ++failedTests );
625633
switch( currentState ) {
626634
case TestState.ExecutingValidations:
@@ -928,6 +936,7 @@ const setupPage = () => {
928936
if(startTestRunnerBtnLbl === '') {
929937
startTestRunnerBtnLbl = document.querySelector('#startTestRunnerBtnLbl').textContent;
930938
}
939+
931940
if( $('#APICalendarSelect').children().length === 1 ) {
932941
nations.forEach( item => {
933942
if ( false === CalendarNations.includes( item ) && item !== "VA" ) {
@@ -1008,6 +1017,8 @@ const setupPage = () => {
10081017
$( '.calendardata-tests' ).find( `.year-${Years[ idx ]}` ).after( NationalCalendarTemplates.join( '' ) );
10091018
$( '.calendardata-tests' ).find( `.year-${Years[ idx ]}` ).siblings( '.file-exists,.json-valid,.schema-valid' ).addClass( `year-${Years[ idx ]}` );
10101019
}
1020+
} else {
1021+
document.querySelectorAll('.error-tooltip').forEach(el => el.remove());
10111022
}
10121023

10131024
$('#specificUnitTestsAccordion').empty();
@@ -1105,7 +1116,7 @@ $( document ).on( 'click', '#startTestRunnerBtn', () => {
11051116
messageCounter = 0;
11061117
successfulTests = 0;
11071118
failedTests = 0;
1108-
currentState = conn.readyState !== WebSocket.CLOSED && conn.ReadyState !== WebSocket.CLOSING ? TestState.ReadyState : TestState.JobsFinished;
1119+
currentState = ( conn.readyState !== WebSocket.CLOSED && conn.ReadyState !== WebSocket.CLOSING ) ? TestState.ReadyState : TestState.JobsFinished;
11091120
if ( conn.readyState !== WebSocket.OPEN ) {
11101121
console.warn( 'cannot run tests: websocket connection is not ready' );
11111122
console.warn( conn.readyState.toString );
@@ -1122,6 +1133,51 @@ $( document ).on( 'click', '#startTestRunnerBtn', () => {
11221133
}
11231134
});
11241135

1136+
// Store tooltips so we can hide them later
1137+
const tooltipMap = new Map();
1138+
1139+
// Show tooltip on click
1140+
document.body.addEventListener('click', function (event) {
1141+
const target = event.target.closest('[data-bs-toggle="tooltip"]');
1142+
if (!target) {
1143+
// Clicked elsewhere: hide all tooltips
1144+
tooltipMap.forEach(t => t.hide());
1145+
tooltipMap.clear();
1146+
return;
1147+
}
1148+
1149+
event.stopPropagation(); // Prevent document click from immediately hiding it
1150+
1151+
// If tooltip already exists, show it
1152+
let tooltip = tooltipMap.get(target);
1153+
if (!tooltip) {
1154+
tooltip = new bootstrap.Tooltip(target, {
1155+
trigger: 'manual',
1156+
html: true,
1157+
customClass: 'wide-tooltip'
1158+
});
1159+
tooltipMap.set(target, tooltip);
1160+
}
1161+
1162+
tooltip.show();
1163+
});
1164+
1165+
// Optional: Hide tooltip on ESC key
1166+
document.addEventListener('keydown', function (event) {
1167+
if (event.key === 'Escape') {
1168+
tooltipMap.forEach(t => t.hide());
1169+
tooltipMap.clear();
1170+
}
1171+
});
1172+
1173+
/*
1174+
const tooltip = new bootstrap.Tooltip(document.body, {
1175+
selector: '[data-bs-toggle="tooltip"]',
1176+
html: true,
1177+
customClass: 'wide-tooltip',
1178+
trigger: 'hover focus'
1179+
});
1180+
*/
11251181
setEndpoints();
11261182
fetchMetadataAndTests();
11271183
connectWebSocket();

components/NewTestModal.php

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,9 @@ class="active" aria-current="true" aria-label="Slide 1"></button>
1818
<form class="row justify-content-left needs-validation" novalidate>
1919
<div class="carousel-item active" data-item='0'>
2020
<div class="form-group">
21-
<label for="existingFestivityName" class="fw-bold"><?php echo _("Choose the liturgical event for which you would like to create a test"); ?>:</label>
22-
<input list="existingFestivitiesList" class="form-control existingFestivityName" id="existingFestivityName" required>
23-
<div class="invalid-feedback"><?php echo _("This festivity does not seem to exist? Please choose from a value in the list."); ?></div>
21+
<label for="existingLitEventName" class="fw-bold"><?php echo _("Choose the liturgical event for which you would like to create a test"); ?>:</label>
22+
<input list="existingLitEventsList" class="form-control existingLitEventName" id="existingLitEventName" required>
23+
<div class="invalid-feedback"><?php echo _("This liturgical event does not seem to exist? Please choose from a value in the list."); ?></div>
2424
</div>
2525
<div class="form-group mt-5">
2626
<label for="newUnitTestDescription"><?php echo _('Description') ?></label>
@@ -92,22 +92,22 @@ class="active" aria-current="true" aria-label="Slide 1"></button>
9292
</div>
9393
</div>
9494

95-
<datalist id="existingFestivitiesList">
95+
<datalist id="existingLitEventsList">
9696
<?php
97-
foreach ($LitCalAllFestivities as $key => $festivity) {
97+
foreach ($LitCalAllLitEvents as $key => $litEvent) {
9898
$dataMonth = '';
9999
$dataDay = '';
100100
$dataGrade = '';
101-
if (isset($festivity["MONTH"])) {
102-
$dataMonth = " data-month=\"{$festivity["MONTH"]}\"";
101+
if (isset($litEvent["MONTH"])) {
102+
$dataMonth = " data-month=\"{$litEvent["MONTH"]}\"";
103103
}
104-
if (isset($festivity["DAY"])) {
105-
$dataDay = " data-day=\"{$festivity["DAY"]}\"";
104+
if (isset($litEvent["DAY"])) {
105+
$dataDay = " data-day=\"{$litEvent["DAY"]}\"";
106106
}
107-
if (isset($festivity["GRADE"])) {
108-
$dataGrade = " data-grade=\"{$festivity["GRADE"]}\"";
107+
if (isset($litEvent["GRADE"])) {
108+
$dataGrade = " data-grade=\"{$litEvent["GRADE"]}\"";
109109
}
110-
echo "<option value=\"{$key}\"{$dataMonth}{$dataDay}{$dataGrade}>{$festivity["NAME"]} ({$festivity["GRADE_LCL"]})</option>";
110+
echo "<option value=\"{$key}\"{$dataMonth}{$dataDay}{$dataGrade}>{$litEvent["NAME"]} ({$litEvent["GRADE_LCL"]})</option>";
111111
}
112112
?>
113113
</datalist>

composer.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,8 @@
22
"require": {
33
"liturgical-calendar/components": "^3.1",
44
"vlucas/phpdotenv": "^5.6"
5+
},
6+
"require-dev": {
7+
"squizlabs/php_codesniffer": "*"
58
}
69
}

0 commit comments

Comments
 (0)