|
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