|
1 | 1 | <!DOCTYPE html> |
2 | 2 | <html lang="en"> |
3 | | -<head> |
4 | | - <meta charset="UTF-8"> |
5 | | - <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
6 | | - <title>GitHub Issue List</title> |
| 3 | + <head> |
| 4 | + <meta charset="UTF-8" /> |
| 5 | + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> |
| 6 | + <title>GitHub-Style Issue Tracker</title> |
| 7 | + <link |
| 8 | + href=" https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" |
| 9 | + rel="stylesheet" |
| 10 | + /> |
| 11 | + <link |
| 12 | + rel="stylesheet" |
| 13 | + href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/css/all.min.css" |
| 14 | + /> |
| 15 | + <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script> |
7 | 16 | <style> |
8 | | - body { |
9 | | - font-family: Arial, sans-serif; |
10 | | - background-color: #f9f9f9; |
11 | | - color: #333; |
12 | | - padding: 20px; |
13 | | - } |
14 | | - h1 { |
15 | | - text-align: center; |
16 | | - color: #333; |
17 | | - } |
18 | | - .issue { |
19 | | - background-color: #fff; |
20 | | - border-radius: 8px; |
21 | | - box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1); |
22 | | - padding: 20px; |
23 | | - margin-bottom: 20px; |
24 | | - border-left: 5px solid #4CAF50; |
25 | | - } |
26 | | - .issue h2 { |
27 | | - color: #333; |
28 | | - margin-top: 0; |
29 | | - } |
30 | | - .issue p { |
31 | | - line-height: 1.6; |
32 | | - } |
33 | | - .issue .comments { |
34 | | - margin-top: 15px; |
35 | | - padding-left: 20px; |
36 | | - border-left: 3px solid #ddd; |
37 | | - } |
38 | | - .comment { |
39 | | - background-color: #f9f9f9; |
40 | | - margin-bottom: 10px; |
41 | | - padding: 10px; |
42 | | - border-radius: 5px; |
43 | | - } |
44 | | - .comment p { |
45 | | - margin: 5px 0; |
46 | | - } |
47 | | - .comment .author { |
48 | | - font-weight: bold; |
49 | | - color: #4CAF50; |
50 | | - } |
51 | | - .comment .date { |
52 | | - font-size: 0.9em; |
53 | | - color: #888; |
54 | | - } |
55 | | - .comment .body { |
56 | | - margin-top: 5px; |
57 | | - } |
58 | | - img { |
59 | | - max-width: 100%; |
60 | | - height: auto; |
61 | | - border-radius: 8px; |
62 | | - } |
| 17 | + body { |
| 18 | + background-color: #f6f8fa; |
| 19 | + } |
| 20 | + |
| 21 | + .issue-card { |
| 22 | + background: white; |
| 23 | + border: 1px solid #d0d7de; |
| 24 | + padding: 16px; |
| 25 | + border-radius: 6px; |
| 26 | + margin-bottom: 10px; |
| 27 | + cursor: pointer; |
| 28 | + } |
| 29 | + |
| 30 | + .issue-title { |
| 31 | + font-size: 20px; |
| 32 | + font-weight: bold; |
| 33 | + color: #0969da; |
| 34 | + } |
| 35 | + |
| 36 | + .badge-open { |
| 37 | + background-color: #2da44e; |
| 38 | + color: white; |
| 39 | + font-size: 12px; |
| 40 | + padding: 5px 8px; |
| 41 | + border-radius: 20px; |
| 42 | + } |
| 43 | + |
| 44 | + .badge-closed { |
| 45 | + background-color: #cf222e; |
| 46 | + color: white; |
| 47 | + font-size: 12px; |
| 48 | + padding: 5px 8px; |
| 49 | + border-radius: 20px; |
| 50 | + } |
| 51 | + |
| 52 | + .comment-box { |
| 53 | + border-left: 3px solid #d0d7de; |
| 54 | + padding-left: 10px; |
| 55 | + margin-top: 10px; |
| 56 | + } |
| 57 | + |
| 58 | + .filter-btn { |
| 59 | + cursor: pointer; |
| 60 | + } |
| 61 | + |
| 62 | + img { |
| 63 | + max-width: 100%; |
| 64 | + display: block; |
| 65 | + padding: 10px 0; |
| 66 | + height: auto; |
| 67 | + } |
| 68 | + |
| 69 | + .issue-number { |
| 70 | + font-size: 20px; |
| 71 | + font-weight: bold; |
| 72 | + color: #0057bb; |
| 73 | + } |
| 74 | + |
| 75 | + code { |
| 76 | + padding: 2px 5px; |
| 77 | + |
| 78 | + background-color: #656c7633; |
| 79 | + border-radius: 0.375rem; |
| 80 | + color: black; |
| 81 | + } |
| 82 | + a { |
| 83 | + color: #0366d6; |
| 84 | + } |
63 | 85 | </style> |
64 | | -</head> |
65 | | -<body> |
66 | | - |
67 | | - <h1>GitHub Issue List</h1> |
68 | | - <div id="issues-container"></div> |
69 | | - |
70 | 86 | <script> |
71 | | - // Function to render issues dynamically |
72 | | - async function fetchIssues() { |
73 | | - try { |
74 | | - const response = await fetch('issues_with_comments_and_images.json'); // Fetch the JSON file |
75 | | - const issuesData = await response.json(); // Parse the JSON data |
76 | | - |
77 | | - // Now render the issues |
78 | | - renderIssues(issuesData); |
79 | | - } catch (error) { |
80 | | - console.error("Error fetching the issues data:", error); |
81 | | - alert("There was an error loading the issues."); |
82 | | - } |
| 87 | + let issues = []; |
| 88 | + |
| 89 | + async function fetchIssues() { |
| 90 | + try { |
| 91 | + const response = await fetch("issues_with_comments_and_images.json"); |
| 92 | + issues = await response.json(); |
| 93 | + displayIssues(issues); |
| 94 | + } catch (error) { |
| 95 | + console.error("Error fetching the issues data:", error); |
| 96 | + alert("There was an error loading the issues."); |
83 | 97 | } |
84 | | - |
85 | | - // Function to render issues dynamically |
86 | | - function renderIssues(issuesData) { |
87 | | - const container = document.getElementById('issues-container'); |
88 | | - issuesData.forEach(issue => { |
89 | | - // Create the issue container |
90 | | - const issueDiv = document.createElement('div'); |
91 | | - issueDiv.classList.add('issue'); |
92 | | - |
93 | | - const numberAndState = document.createElement('h3'); |
94 | | - numberAndState.innerHTML = `<strong>Issue #${issue.number}</strong> - State: ${issue.state==="OPEN" ? `<span style="color: green;">Open</span>` : `<span style="color: red;">Closed</span>`}`; |
95 | | - issueDiv.appendChild(numberAndState); |
96 | | - |
97 | | - // Title and number |
98 | | - const title = document.createElement('h2'); |
99 | | - title.textContent = issue.title; |
100 | | - issueDiv.appendChild(title); |
101 | | - |
102 | | - |
103 | | - |
104 | | - // Body - converting Markdown images into HTML <img> tags |
105 | | - const body = document.createElement('p'); |
106 | | - body.innerHTML = processBodyText(issue.body); |
107 | | - issueDiv.appendChild(body); |
108 | | - |
109 | | - // Comments Section |
110 | | - if (issue.comments.length > 0) { |
111 | | - const commentsDiv = document.createElement('div'); |
112 | | - commentsDiv.classList.add('comments'); |
113 | | - |
114 | | - issue.comments.forEach(comment => { |
115 | | - const commentDiv = document.createElement('div'); |
116 | | - commentDiv.classList.add('comment'); |
117 | | - |
118 | | - const author = document.createElement('p'); |
119 | | - author.classList.add('author'); |
120 | | - author.innerHTML = `${comment.author} <span class="date">(${comment.date})</span>`; |
121 | | - commentDiv.appendChild(author); |
122 | | - |
123 | | - const body = document.createElement('p'); |
124 | | - body.classList.add('body'); |
125 | | - body.textContent = comment.body; |
126 | | - commentDiv.appendChild(body); |
127 | | - |
128 | | - commentsDiv.appendChild(commentDiv); |
129 | | - }); |
130 | | - |
131 | | - issueDiv.appendChild(commentsDiv); |
132 | | - } |
133 | | - |
134 | | - container.appendChild(issueDiv); |
135 | | - }); |
| 98 | + } |
| 99 | + |
| 100 | + function fixBodyForImages(markdown) { |
| 101 | + return markdown.replace( |
| 102 | + /!\[.*?\]\(https:\/\/github\.com\/user-attachments\/assets\/([\w-]+)\)/g, |
| 103 | + "" // Replace with local path |
| 104 | + ); |
| 105 | + } |
| 106 | + |
| 107 | + function displayIssues(issues) { |
| 108 | + const issueContainer = document.getElementById("issue-list"); |
| 109 | + issueContainer.innerHTML = ""; |
| 110 | + |
| 111 | + issues.forEach((issue) => { |
| 112 | + const issueCard = document.createElement("div"); |
| 113 | + issueCard.classList.add("issue-card"); |
| 114 | + |
| 115 | + issueCard.innerHTML = ` |
| 116 | + <div class="d-flex justify-content-between align-items-center" onclick="toggleCollapse('issue-${ |
| 117 | + issue.number |
| 118 | + }')"> |
| 119 | + <div> |
| 120 | + <b class="issue-number">#${issue.number}</b> |
| 121 | + <i class="fa-solid ${ |
| 122 | + issue.state === "OPEN" |
| 123 | + ? "fa-exclamation-circle text-success" |
| 124 | + : "fa-check-circle text-danger" |
| 125 | + }"></i> |
| 126 | + <span class="issue-title">${issue.title}</span> |
| 127 | + <span class="${ |
| 128 | + issue.state === "OPEN" |
| 129 | + ? "badge-open" |
| 130 | + : "badge-closed" |
| 131 | + }">${issue.state}</span> |
| 132 | + </div> |
| 133 | + <i class="fa-solid fa-chevron-down"></i> |
| 134 | + </div> |
| 135 | +
|
| 136 | + <div id="issue-${issue.number}" class="collapse mt-2"> |
| 137 | + |
| 138 | + <p>${marked.parse(fixBodyForImages(issue.body))}</p> |
| 139 | +
|
| 140 | + <button class="btn btn-sm btn-outline-secondary mt-2" onclick="toggleCollapse('comments-${ |
| 141 | + issue.number |
| 142 | + }')"> |
| 143 | + <i class="fa-solid fa-comments"></i> Show Comments ( ${ |
| 144 | + issue.comments.length |
| 145 | + } ) |
| 146 | + </button> |
| 147 | +
|
| 148 | + <div id="comments-${ |
| 149 | + issue.number |
| 150 | + }" class="collapse mt-2"> |
| 151 | + ${ |
| 152 | + issue.comments.length > 0 |
| 153 | + ? issue.comments |
| 154 | + .map( |
| 155 | + (comment) => ` |
| 156 | + <div class="comment-box"> |
| 157 | + <strong> |
| 158 | + <a class="text-black fs-5" href="https://github.com/${ |
| 159 | + comment.author |
| 160 | + }" target="_blank">${comment.author}</a> |
| 161 | + </strong> |
| 162 | + <p>${marked.parse(comment.body)}</p> |
| 163 | + </div> |
| 164 | + ` |
| 165 | + ) |
| 166 | + .join("") |
| 167 | + : "<p>No comments available.</p>" |
| 168 | + } |
| 169 | + </div> |
| 170 | + </div> |
| 171 | + `; |
| 172 | + |
| 173 | + issueContainer.appendChild(issueCard); |
| 174 | + }); |
| 175 | + } |
| 176 | + |
| 177 | + function toggleCollapse(id) { |
| 178 | + const element = document.getElementById(id); |
| 179 | + if (element) { |
| 180 | + element.classList.toggle("collapse"); |
136 | 181 | } |
| 182 | + } |
137 | 183 |
|
138 | | - // Function to process body text and convert Markdown-style images to <img> tags |
139 | | - function processBodyText(text) { |
140 | | - // Regex to match Markdown image syntax (e.g. ) |
141 | | - const regex = /!\[([^\]]+)\]\((https?:\/\/[^\)]+)\)/g; |
142 | | - |
143 | | - // Replace Markdown images with <img> tags pointing to the local 'downloaded_images' folder |
144 | | - return text.replace(regex, (match, altText, url) => { |
145 | | - // Extract image name from the URL |
146 | | - const imageName = url.split('/').pop(); // Get the last part of the URL as the image file name |
147 | | - |
148 | | - // Check if the image has an extension and handle accordingly |
149 | | - const imageExtension = imageName.split('.').pop(); |
150 | | - const imagePath = `./downloaded_images/${imageName}`; |
151 | | - |
152 | | - // Debugging: log the URL and local path to verify |
153 | | - console.log("Original URL:", url); // Check the original URL |
154 | | - console.log("Local Image Path:", imagePath); // Check the local image path |
155 | | - |
156 | | - // Return HTML <img> tag |
157 | | - return `<img src="${imagePath}" alt="${altText}" style="max-width: 100%; height: auto;">`; |
158 | | - }).replace(/\n/g, '<br>'); // Replace line breaks with <br> tags |
159 | | - } |
| 184 | + function filterIssues(state) { |
| 185 | + const filteredIssues = |
| 186 | + state === "ALL" |
| 187 | + ? issues |
| 188 | + : issues.filter((issue) => issue.state === state); |
| 189 | + displayIssues(filteredIssues); |
| 190 | + } |
160 | 191 |
|
161 | | - // Call the fetchIssues function when the page loads |
162 | | - fetchIssues(); |
| 192 | + document.addEventListener("DOMContentLoaded", fetchIssues); |
163 | 193 | </script> |
164 | | - |
165 | | -</body> |
| 194 | + </head> |
| 195 | + |
| 196 | + <body class="container-fluid mt-4"> |
| 197 | + <h2 class="text-center"> |
| 198 | + <i class="fa-solid fa-bug"></i> GitHub-Style Issue Tracker |
| 199 | + </h2> |
| 200 | + |
| 201 | + <div class="d-flex justify-content-center"> |
| 202 | + <div class="dropdown mb-3"> |
| 203 | + <button class="btn btn-outline-secondary dropdown-toggle" type="button" id="filterDropdown" data-bs-toggle="dropdown" aria-expanded="false"> |
| 204 | + <i class="fa-solid fa-filter"></i> Filter Issues |
| 205 | + </button> |
| 206 | + <ul class="dropdown-menu" aria-labelledby="filterDropdown"> |
| 207 | + <li><a class="dropdown-item text-success" href="#" onclick="filterIssues('OPEN')"> |
| 208 | + <i class="fa-solid fa-exclamation-circle"></i> Open Issues |
| 209 | + </a></li> |
| 210 | + <li><a class="dropdown-item text-danger" href="#" onclick="filterIssues('CLOSED')"> |
| 211 | + <i class="fa-solid fa-check-circle"></i> Closed Issues |
| 212 | + </a></li> |
| 213 | + <li><a class="dropdown-item text-secondary" href="#" onclick="filterIssues('ALL')"> |
| 214 | + <i class="fa-solid fa-list"></i> All Issues |
| 215 | + </a></li> |
| 216 | + </ul> |
| 217 | + </div> |
| 218 | + </div> |
| 219 | + |
| 220 | + <div id="issue-list"></div> |
| 221 | + |
| 222 | + <footer class="text-center mt-4"> |
| 223 | + Developed by <a href="https://github.com/nazmul-rion" target="_blank">Nazmul Islam Rion</a> |
| 224 | + </footer> |
| 225 | + |
| 226 | + |
| 227 | + <script src=" https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js" ></script> |
| 228 | + </body> |
166 | 229 | </html> |
0 commit comments