|
4 | 4 |
|
5 | 5 | const ISSUES_URL = 'https://api.github.com/repos/animated-java/animated-java/issues/' |
6 | 6 |
|
7 | | - const FORMATTED_CHANGELOG_CACHE = new Map<string, string>() |
8 | | -
|
9 | 7 | const formatDateFull = (date: string) => { |
10 | 8 | // @ts-expect-error No types for getDateDisplay |
11 | 9 | return getDateDisplay(date).full |
|
15 | 13 | return getDateDisplay(date).short |
16 | 14 | } |
17 | 15 |
|
18 | | - const formatMarkdown = async (text: string) => { |
19 | | - const issues: Record<number, { title: string; url: string }> = {} |
20 | | - text = text.replace('[BREAKING]', '<span class="breaking">BREAKING</span>') |
21 | | - text = text.replace(/\[([^\]]+?)\]\(([^)]+?)\)/gm, (match, title, url) => { |
22 | | - const issueMatch = url.match(/issues\/(\d+)/) |
23 | | - if (issueMatch) { |
24 | | - const issueNumber = parseInt(issueMatch[1]) |
25 | | - issues[issueNumber] = { title, url } |
26 | | - return `$$$ISSUE${issueNumber}$$$` |
27 | | - } |
28 | | - return `<a href="${url}" target="_blank">${title}</a>` |
29 | | - }) |
30 | | - for (const [issueNumber, { url }] of Object.entries(issues)) { |
31 | | - const data = await fetch(ISSUES_URL + issueNumber) |
32 | | - .then(response => response.json()) |
33 | | - .catch(() => undefined) |
34 | | - text = text.replace( |
35 | | - `$$$ISSUE${issueNumber}$$$`, |
36 | | - `<a href="${url}" target="_blank">#${issueNumber}${data ? ' - ' + data.title : ''}</a>` |
37 | | - ) |
38 | | - } |
39 | | - // inline code blocks |
40 | | - text = text.replace(/`([^`]+?)`/g, '<code>$1</code>') |
41 | | - return text |
| 16 | + interface GithubIssue { |
| 17 | + number: number |
| 18 | + title: string |
| 19 | + html_url: string |
42 | 20 | } |
43 | 21 |
|
44 | | - const getChangelog = async (item: string) => { |
45 | | - if (FORMATTED_CHANGELOG_CACHE.has(item)) { |
46 | | - return FORMATTED_CHANGELOG_CACHE.get(item) |
47 | | - } |
48 | | - const formatted = await formatMarkdown(item) |
49 | | - FORMATTED_CHANGELOG_CACHE.set(item, formatted) |
50 | | - return formatted |
| 22 | + const ISSUES = new Map<string, GithubIssue>() |
| 23 | +
|
| 24 | + const fetchIssue = async (issueId: string): Promise<GithubIssue | undefined> => { |
| 25 | + if (ISSUES.has(issueId)) return ISSUES.get(issueId) |
| 26 | +
|
| 27 | + const issue = await fetch(ISSUES_URL + issueId) |
| 28 | + .then(async response => { |
| 29 | + const json = await response.json() |
| 30 | + if (!response.ok) |
| 31 | + throw new Error( |
| 32 | + `Failed to fetch issue ${issueId}: ` + (json.message ?? response.statusText) |
| 33 | + ) |
| 34 | + return json |
| 35 | + }) |
| 36 | + .catch(e => { |
| 37 | + console.error(`Failed to fetch issue ${issueId}`, e) |
| 38 | + return undefined |
| 39 | + }) |
| 40 | + if (issue) ISSUES.set(issueId, issue) |
| 41 | + return issue |
51 | 42 | } |
52 | 43 | </script> |
53 | 44 |
|
54 | 45 | <div class="content plugin_browser_tabbed_page" id="plugin_browser_changelog"> |
55 | | - {#each Object.values(changelog).reverse() as versions} |
| 46 | + {#each Object.values(changelog).reverse().slice(0, 4) as versions} |
56 | 47 | <div class="title-container"> |
57 | 48 | <img src={AnimatedJavaIcon} alt="" /> |
58 | 49 | <h3> |
|
70 | 61 | <ul> |
71 | 62 | {#each versions.categories as category} |
72 | 63 | <li> |
73 | | - <h4>{category.title}</h4> |
| 64 | + <h4> |
| 65 | + {category.title} |
| 66 | + </h4> |
74 | 67 | <ul class="plugin_changelog_features"> |
75 | 68 | {#each category.list as item} |
76 | | - <li> |
77 | | - {#await getChangelog(item) then data} |
78 | | - {@html data} |
| 69 | + {@const issueMatch = /^Fixed \[#(\d+)\]\(.+?\)$/.exec(item)} |
| 70 | + {#if issueMatch} |
| 71 | + {#await fetchIssue(issueMatch[1])} |
| 72 | + <li> |
| 73 | + <p> |
| 74 | + <i class="material-icons icon spinner" |
| 75 | + >progress_activity</i |
| 76 | + > |
| 77 | + <!-- svelte-ignore missing-declaration --> |
| 78 | + {@html pureMarked(item)} |
| 79 | + </p> |
| 80 | + </li> |
| 81 | + {:then issue} |
| 82 | + <li> |
| 83 | + <p> |
| 84 | + {'Fixed '} |
| 85 | + {#if issue} |
| 86 | + <a href={issue.html_url} target="_blank"> |
| 87 | + {'#' + issue.number} |
| 88 | + </a> |
| 89 | + {' - '} |
| 90 | + <!-- svelte-ignore missing-declaration --> |
| 91 | + {@html pureMarked(issue.title)} |
| 92 | + {:else} |
| 93 | + <a |
| 94 | + href="${ISSUES_URL + issueMatch[1]}" |
| 95 | + target="_blank" |
| 96 | + > |
| 97 | + {'#' + issueMatch[1]} |
| 98 | + </a> |
| 99 | + {/if} |
| 100 | + </p> |
| 101 | + </li> |
| 102 | + {:catch} |
| 103 | + <li> |
| 104 | + <!-- svelte-ignore missing-declaration --> |
| 105 | + {@html pureMarked(item)} |
| 106 | + </li> |
79 | 107 | {/await} |
80 | | - </li> |
| 108 | + {:else if item.startsWith('[BREAKING]')} |
| 109 | + <li> |
| 110 | + <p> |
| 111 | + <span class="breaking">BREAKING</span> |
| 112 | + <!-- svelte-ignore missing-declaration --> |
| 113 | + {@html pureMarked(item.replace('[BREAKING]', '').trim())} |
| 114 | + </p> |
| 115 | + </li> |
| 116 | + {:else} |
| 117 | + <li> |
| 118 | + <!-- svelte-ignore missing-declaration --> |
| 119 | + {@html pureMarked(item)} |
| 120 | + </li> |
| 121 | + {/if} |
81 | 122 | {/each} |
82 | 123 | </ul> |
83 | 124 | </li> |
84 | 125 | {/each} |
85 | 126 | </ul> |
86 | 127 | <hr /> |
87 | 128 | {/each} |
| 129 | + <p class="disclaimer">To see the full changelog, open Animated Java in the plugin list.</p> |
88 | 130 | </div> |
89 | 131 |
|
90 | 132 | <style> |
|
105 | 147 | border-radius: 3px; |
106 | 148 | font-size: 0.9em; |
107 | 149 | font-weight: bold; |
108 | | - margin-right: 0.25rem; |
| 150 | + height: min-content; |
109 | 151 | } |
110 | 152 | img { |
111 | 153 | border-radius: 4px; |
|
128 | 170 | hr { |
129 | 171 | margin: 2rem 0; |
130 | 172 | } |
| 173 | + .disclaimer { |
| 174 | + font-size: 0.9em; |
| 175 | + color: var(--color-text-secondary); |
| 176 | + font-style: italic; |
| 177 | + text-align: center; |
| 178 | + margin-top: 1rem; |
| 179 | + } |
| 180 | + li > p { |
| 181 | + display: flex; |
| 182 | + flex-direction: row; |
| 183 | + gap: 0.5rem; |
| 184 | + align-items: flex-start; |
| 185 | + } |
| 186 | + @keyframes spin { |
| 187 | + from { |
| 188 | + transform: rotate(0deg); |
| 189 | + } |
| 190 | + to { |
| 191 | + transform: rotate(360deg); |
| 192 | + } |
| 193 | + } |
| 194 | + .spinner { |
| 195 | + animation: spin 1s linear infinite; |
| 196 | + } |
131 | 197 | </style> |
0 commit comments