-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathindex.html
More file actions
364 lines (322 loc) · 17 KB
/
index.html
File metadata and controls
364 lines (322 loc) · 17 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Markdown Table Viewer</title>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<style>
body {
font-family: 'Inter', sans-serif;
background-color: #f3f4f6; /* Tailwind gray-100 */
}
/* Loader styles */
.loader {
border: 8px solid #f3f3f3; /* Light grey */
border-top: 8px solid #3498db; /* Blue */
border-radius: 50%;
width: 60px;
height: 60px;
animation: spin 1s linear infinite;
position: fixed; /* Changed to fixed for better visibility during load */
top: 50%;
left: 50%;
transform: translate(-50%, -50%); /* Center loader */
z-index: 100; /* Ensure loader is on top */
}
@keyframes spin {
0% { transform: translate(-50%, -50%) rotate(0deg); }
100% { transform: translate(-50%, -50%) rotate(360deg); }
}
.table-container {
max-height: 70vh; /* Adjusted height for search bar */
overflow: auto;
width: 100%;
}
th, td {
min-width: 150px; /* Ensure columns have a minimum width */
padding: 0.75rem; /* Tailwind p-3 equivalent */
border-bottom-width: 1px; /* From divide-y */
}
th {
position: sticky; /* Make header sticky */
top: 0;
background-color: #f9fafb; /* Tailwind gray-50 */
z-index: 10; /* Keep header above table content during scroll */
text-align: left; /* Default alignment */
}
td {
white-space: nowrap; /* Keep cell content on one line */
overflow: hidden; /* Hide overflow */
text-overflow: ellipsis; /* Add ellipsis for overflowed text */
max-width: 400px; /* Prevent extremely wide cells, adjust as needed */
}
/* Allow td content to be seen on hover if truncated */
td:hover {
white-space: normal;
overflow: visible;
max-width: none;
}
#searchInput {
padding: 0.75rem 1rem;
border: 1px solid #d1d5db; /* Tailwind gray-300 */
border-radius: 0.375rem; /* Tailwind rounded-md */
width: 100%;
max-width: 500px; /* Limit search bar width */
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05); /* Tailwind shadow-sm */
}
#uniqueErrorCount {
font-size: 0.9em; /* Slightly smaller than title */
color: #6b7280; /* Tailwind gray-500 */
font-weight: 500; /* Medium weight */
margin-left: 0.5rem;
}
</style>
</head>
<body class="bg-gray-100 text-gray-800 min-h-screen flex flex-col items-center p-4 pt-8">
<div id="loader" class="loader"></div>
<div id="errorMessage" class="hidden bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded-lg relative mb-4 w-full max-w-4xl text-center" role="alert">
<strong class="font-bold">Error!</strong>
<span class="block sm:inline" id="errorText"></span>
</div>
<div class="w-full max-w-7xl bg-white shadow-xl rounded-lg p-6 hidden flex flex-col" id="contentContainer">
<h1 class="text-3xl font-bold mb-4 text-center text-gray-700">
PHP Error Report Table <span id="uniqueErrorCount"></span>
</h1>
<div class="mb-6 flex justify-center">
<input type="text" id="searchInput" placeholder="Search by Item or Message..." class="focus:ring-indigo-500 focus:border-indigo-500">
</div>
<div id="tableContainer" class="table-container rounded-lg border border-gray-200">
</div>
<p id="rowCount" class="text-sm text-gray-600 mt-4 text-center"></p>
</div>
<script>
// URL of the Markdown file
const markdownFileUrl = 'https://raw.githubusercontent.com/bgrgicak/Playground-compatibility-reports/refs/heads/main/reports/php-errors.md';
// DOM elements
const loader = document.getElementById('loader');
const tableContainer = document.getElementById('tableContainer');
const contentContainer = document.getElementById('contentContainer');
const errorMessageDiv = document.getElementById('errorMessage');
const errorTextSpan = document.getElementById('errorText');
const searchInput = document.getElementById('searchInput');
const rowCountDisplay = document.getElementById('rowCount');
const uniqueErrorCountSpan = document.getElementById('uniqueErrorCount');
/**
* Fetches and processes the Markdown file.
*/
async function fetchAndProcessMarkdown() {
showLoader(true);
showError(false);
contentContainer.classList.add('hidden');
uniqueErrorCountSpan.textContent = ""; // Clear previous count
try {
const response = await fetch(markdownFileUrl);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status} - ${response.statusText}. URL: ${markdownFileUrl}`);
}
const markdownText = await response.text();
// console.log("Fetched Markdown (first 500 chars):", markdownText.substring(0, 500)); // For debugging
const parsedResult = parseMarkdownTable(markdownText);
if (parsedResult && parsedResult.html && parsedResult.uniqueCount > 0) {
tableContainer.innerHTML = parsedResult.html;
uniqueErrorCountSpan.textContent = `(${parsedResult.uniqueCount} unique)`;
contentContainer.classList.remove('hidden');
searchInput.addEventListener('keyup', filterTable);
updateRowCount();
} else {
showError(true, 'No table found, required columns (Item, Message) are missing, or no unique errors to display. Check console for details.');
}
} catch (error) {
console.error('Error fetching or parsing Markdown:', error);
showError(true, `Failed to load or parse the Markdown file. ${error.message}`);
} finally {
showLoader(false);
}
}
/**
* Parses Markdown text to find the first table, de-duplicates by "Message",
* and converts it to HTML with only "Item" and "Message" columns.
* @param {string} markdown - The Markdown content.
* @returns {{html: string, uniqueCount: number}|null} Object with HTML string and count, or null.
*/
function parseMarkdownTable(markdown) {
const lines = markdown.split('\n');
// console.log("Markdown lines (first 10 for debug):", lines.slice(0, 10)); // For debugging
let tableStarted = false;
let headerProcessed = false;
let originalHeaderCells = [];
let itemIndex = -1;
let messageIndex = -1;
const newTableRows = [];
const seenMessages = new Set(); // To track unique messages
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
const trimmedLine = line.trim();
if (trimmedLine.startsWith('|') && trimmedLine.endsWith('|')) {
const cells = trimmedLine.slice(1, -1).split('|').map(cell => cell.trim());
if (!tableStarted) {
// console.log("Potential header line:", trimmedLine); // For debugging
const nextLineIndex = i + 1;
if (nextLineIndex < lines.length) {
const nextLine = lines[nextLineIndex].trim();
// console.log("Next line (for separator check):", nextLine); // For debugging
if (nextLine.startsWith('|') && nextLine.includes('-') && nextLine.endsWith('|')) {
const separatorCells = nextLine.slice(1, -1).split('|').map(sc => sc.trim());
if (separatorCells.length === cells.length && separatorCells.every(sc => sc.match(/^:?-+:?$/))) {
// console.log("Table started. Header identified:", cells); // For debugging
tableStarted = true;
originalHeaderCells = cells;
itemIndex = originalHeaderCells.findIndex(h => h.toLowerCase() === 'item');
messageIndex = originalHeaderCells.findIndex(h => h.toLowerCase() === 'message');
// console.log(`Indices found - Item: ${itemIndex}, Message: ${messageIndex}`); // For debugging
if (itemIndex === -1 || messageIndex === -1) {
console.error("Required columns 'Item' or 'Message' not found in header:", originalHeaderCells);
return null;
}
i++;
headerProcessed = true;
// console.log("Header processed, separator line skipped."); // For debugging
} else {
// console.log("Separator line invalid or mismatch."); // For debugging
}
} else {
// console.log("Next line is not a valid separator."); // For debugging
}
}
} else if (tableStarted && headerProcessed) { // This is a data row
if (cells.length === originalHeaderCells.length) { // Check if cell count matches header
const itemContent = cells[itemIndex];
const messageContent = cells[messageIndex];
// Deduplication based on messageContent
if (!seenMessages.has(messageContent)) {
seenMessages.add(messageContent);
newTableRows.push([itemContent, messageContent]);
} else {
// console.log("Duplicate message skipped:", messageContent); // For debugging
}
} else {
// MODIFIED BEHAVIOR: Log warning and skip row instead of breaking
console.warn(`Data row cell count mismatch. Expected: ${originalHeaderCells.length}, Got: ${cells.length}. Skipping line: "${trimmedLine}"`);
// No break here, loop continues to the next line
}
}
} else { // Line does not start and end with '|'
if (tableStarted && headerProcessed) { // If table processing was underway, assume end of table
// console.log("Non-table line encountered, assuming end of table:", trimmedLine); // For debugging
break;
}
if (tableStarted && !headerProcessed) { // False start for table (e.g. header found but no valid separator)
// console.log("False start for table, resetting."); // For debugging
tableStarted = false;
originalHeaderCells = [];
itemIndex = -1;
messageIndex = -1;
}
}
}
if (itemIndex === -1 || messageIndex === -1 || newTableRows.length === 0) {
console.log("Final check: No valid table data. Item Index:", itemIndex, "Message Index:", messageIndex, "Unique Rows:", newTableRows.length);
return null;
}
// console.log(`Successfully parsed ${newTableRows.length} unique rows for the table.`); // For debugging
let html = '<table class="min-w-full divide-y divide-gray-200">';
html += '<thead class="bg-gray-50">';
html += '<tr>';
html += `<th scope="col" class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Item</th>`;
html += `<th scope="col" class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Message</th>`;
html += '</tr>';
html += '</thead>';
html += '<tbody class="bg-white divide-y divide-gray-200">';
newTableRows.forEach(row => {
const itemCell = row[0];
const messageCell = row[1];
html += '<tr>';
html += `<td class="px-4 py-3 text-sm text-gray-700" title="${escapeHtml(itemCell)}">${parseAndLinkCell(itemCell)}</td>`;
html += `<td class="px-4 py-3 text-sm text-gray-700" title="${escapeHtml(messageCell)}">${parseAndLinkCell(messageCell)}</td>`;
html += '</tr>';
});
html += '</tbody>';
html += '</table>';
return { html: html, uniqueCount: newTableRows.length };
}
function parseAndLinkCell(cellContent) {
const escapedContent = escapeHtml(cellContent);
const linkMatch = cellContent.match(/\[([^\]]+)\]\(([^)]+)\)/);
if (linkMatch && linkMatch[1] !== undefined && linkMatch[2] !== undefined) {
const linkText = escapeHtml(linkMatch[1]);
const linkUrl = escapeHtml(linkMatch[2]);
if (linkUrl.startsWith('http://') || linkUrl.startsWith('https://') || linkUrl.startsWith('/')) {
return `<a href="${linkUrl}" target="_blank" rel="noopener noreferrer" class="text-blue-600 hover:text-blue-800 hover:underline">${linkText}</a>`;
}
}
return escapedContent;
}
function escapeHtml(unsafe) {
if (unsafe === null || typeof unsafe === 'undefined') return '';
return unsafe
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
}
function filterTable() {
const searchTerm = searchInput.value.toLowerCase();
const table = tableContainer.querySelector('table');
if (!table) return;
const tbody = table.getElementsByTagName('tbody')[0];
if (!tbody) return;
const rows = tbody.getElementsByTagName('tr');
let visibleRows = 0;
for (let i = 0; i < rows.length; i++) {
const row = rows[i];
const itemCell = row.cells[0];
const messageCell = row.cells[1];
let match = false;
if (itemCell && messageCell) {
const itemText = (itemCell.textContent || itemCell.innerText || "").toLowerCase();
const messageText = (messageCell.textContent || messageCell.innerText || "").toLowerCase();
if (itemText.includes(searchTerm) || messageText.includes(searchTerm)) {
match = true;
}
}
row.style.display = match ? '' : 'none';
if (match) {
visibleRows++;
}
}
updateRowCount(visibleRows, rows.length);
}
function updateRowCount(visible, total) {
const table = tableContainer.querySelector('table');
if (!table || !table.tBodies[0]) {
rowCountDisplay.textContent = "";
return;
}
const tbody = table.tBodies[0];
const totalRows = total !== undefined ? total : tbody.rows.length;
const visibleRowsCount = visible !== undefined ? visible : totalRows;
if (searchInput.value) {
rowCountDisplay.textContent = `Showing ${visibleRowsCount} of ${totalRows} unique errors.`;
} else {
rowCountDisplay.textContent = `Total ${totalRows} unique errors.`;
}
}
function showLoader(show) {
loader.style.display = show ? 'block' : 'none';
}
function showError(show, message = '') {
if (show) {
errorTextSpan.textContent = message;
errorMessageDiv.classList.remove('hidden');
} else {
errorMessageDiv.classList.add('hidden');
errorTextSpan.textContent = '';
}
}
document.addEventListener('DOMContentLoaded', fetchAndProcessMarkdown);
</script>
</body>
</html>