Skip to content

Commit d47fdbd

Browse files
authored
1 parent c29b973 commit d47fdbd

File tree

1 file changed

+212
-0
lines changed

1 file changed

+212
-0
lines changed

github-issue-to-markdown.html

+212
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<style>
5+
* {
6+
box-sizing: border-box;
7+
}
8+
9+
body {
10+
font-family: Helvetica, Arial, sans-serif;
11+
max-width: 800px;
12+
margin: 0 auto;
13+
padding: 20px;
14+
}
15+
16+
h1 {
17+
margin-bottom: 24px;
18+
}
19+
20+
.input-group {
21+
margin-bottom: 24px;
22+
}
23+
24+
label {
25+
display: block;
26+
margin-bottom: 8px;
27+
}
28+
29+
input {
30+
width: 100%;
31+
padding: 8px;
32+
font-size: 16px;
33+
border: 1px solid #ccc;
34+
border-radius: 4px;
35+
}
36+
37+
button {
38+
background: #2ea44f;
39+
color: white;
40+
border: none;
41+
padding: 8px 16px;
42+
border-radius: 4px;
43+
cursor: pointer;
44+
}
45+
46+
button:hover {
47+
background: #2c974b;
48+
}
49+
50+
button:disabled {
51+
background: #94d3a2;
52+
cursor: not-allowed;
53+
}
54+
55+
.output {
56+
margin-top: 24px;
57+
}
58+
59+
textarea {
60+
width: 100%;
61+
min-height: 300px;
62+
padding: 12px;
63+
font-size: 16px;
64+
font-family: monospace;
65+
border: 1px solid #ccc;
66+
border-radius: 4px;
67+
resize: vertical;
68+
}
69+
70+
.error {
71+
color: #cf222e;
72+
margin-top: 8px;
73+
}
74+
75+
.copy-button {
76+
margin-top: 12px;
77+
}
78+
</style>
79+
</head>
80+
<body>
81+
<h1>Convert GitHub issue to markdown</h1>
82+
83+
<div class="input-group">
84+
<label for="issue-url">GitHub issue URL</label>
85+
<input
86+
type="text"
87+
id="issue-url"
88+
placeholder="https://github.com/owner/repo/issues/123"
89+
>
90+
</div>
91+
92+
<button id="convert">Convert to markdown</button>
93+
94+
<div class="output">
95+
<textarea id="markdown-output" readonly></textarea>
96+
<button class="copy-button" id="copy">Copy to clipboard</button>
97+
</div>
98+
99+
<p class="error" id="error"></p>
100+
101+
<script type="module">
102+
const urlInput = document.getElementById('issue-url')
103+
const convertButton = document.getElementById('convert')
104+
const markdownOutput = document.getElementById('markdown-output')
105+
const copyButton = document.getElementById('copy')
106+
const errorElement = document.getElementById('error')
107+
108+
function parseGitHubUrl(url) {
109+
try {
110+
const urlObj = new URL(url)
111+
const [, owner, repo, , number] = urlObj.pathname.split('/')
112+
return { owner, repo, number }
113+
} catch (e) {
114+
throw new Error('Invalid GitHub URL')
115+
}
116+
}
117+
118+
function convertToMarkdown(issue, comments) {
119+
let md = `# ${issue.title}\n\n`
120+
md += `*Posted by @${issue.user.login}*\n\n`
121+
md += issue.body + '\n\n'
122+
123+
if (comments.length > 0) {
124+
md += '---\n\n'
125+
comments.forEach(comment => {
126+
md += `### Comment by @${comment.user.login}\n\n`
127+
md += comment.body + '\n\n'
128+
md += '---\n\n'
129+
})
130+
}
131+
132+
return md
133+
}
134+
135+
async function getAllPages(url) {
136+
let allItems = []
137+
let nextUrl = url
138+
139+
while (nextUrl) {
140+
const response = await fetch(nextUrl)
141+
142+
if (!response.ok) {
143+
throw new Error('Failed to fetch data')
144+
}
145+
146+
const items = await response.json()
147+
allItems = allItems.concat(items)
148+
149+
// Check for pagination in Link header
150+
const link = response.headers.get('Link')
151+
nextUrl = null
152+
153+
if (link) {
154+
const nextLink = link.split(',').find(s => s.includes('rel="next"'))
155+
if (nextLink) {
156+
nextUrl = nextLink.split(';')[0].trim().slice(1, -1)
157+
}
158+
}
159+
}
160+
161+
return allItems
162+
}
163+
164+
async function fetchIssueAndComments(owner, repo, number) {
165+
const issueUrl = `https://api.github.com/repos/${owner}/${repo}/issues/${number}`
166+
const commentsUrl = `${issueUrl}/comments`
167+
168+
const [issue, comments] = await Promise.all([
169+
fetch(issueUrl).then(res => {
170+
if (!res.ok) throw new Error('Failed to fetch issue')
171+
return res.json()
172+
}),
173+
getAllPages(commentsUrl)
174+
])
175+
176+
return { issue, comments }
177+
}
178+
179+
convertButton.addEventListener('click', async () => {
180+
errorElement.textContent = ''
181+
markdownOutput.value = ''
182+
convertButton.disabled = true
183+
184+
try {
185+
const { owner, repo, number } = parseGitHubUrl(urlInput.value)
186+
const { issue, comments } = await fetchIssueAndComments(owner, repo, number)
187+
const markdown = convertToMarkdown(issue, comments)
188+
markdownOutput.value = markdown
189+
} catch (error) {
190+
errorElement.textContent = error.message
191+
} finally {
192+
convertButton.disabled = false
193+
}
194+
})
195+
196+
copyButton.addEventListener('click', () => {
197+
markdownOutput.select()
198+
document.execCommand('copy')
199+
200+
const originalText = copyButton.textContent
201+
copyButton.textContent = 'Copied'
202+
copyButton.disabled = true
203+
204+
setTimeout(() => {
205+
copyButton.textContent = originalText
206+
copyButton.disabled = false
207+
}, 2000)
208+
})
209+
210+
</script>
211+
</body>
212+
</html>

0 commit comments

Comments
 (0)