|
9 | 9 | import Foundation
|
10 | 10 |
|
11 | 11 | let header = """
|
12 |
| -[](https://vshymanskyy.github.io/StandWithUkraine) |
| 12 | +<div align="center"> |
| 13 | + <a href="https://vshymanskyy.github.io/StandWithUkraine"> |
| 14 | + <img src="https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/banner2-direct.svg" alt="Stand With Ukraine" /> |
| 15 | + </a> |
| 16 | + |
| 17 | + <img src="./icons/icon.png" width="160" height="160"> |
| 18 | + <h1>Awesome macOS Open Source Applications</h1> |
| 19 | + <p>A curated list of open source applications for macOS</p> |
| 20 | + <p> |
| 21 | + <a href="https://github.com/sindresorhus/awesome"><img alt="Awesome" src="https://awesome.re/badge.svg" /></a> |
| 22 | + <a href="https://gitter.im/open-source-mac-os-apps/Lobby"><img alt="Join the chat at gitter" src="https://badges.gitter.im/Join%20Chat.svg" /></a> |
| 23 | + <a href="https://t.me/opensourcemacosapps"><img alt="Telegram Channel" src="https://img.shields.io/badge/Telegram-Channel-blue.svg" /></a> |
| 24 | + </p> |
| 25 | +</div> |
13 | 26 |
|
14 | 27 | <p align="center">
|
15 |
| -<img src="./icons/icon.png"> |
16 |
| -</p> |
17 |
| -
|
18 |
| -# Awesome macOS open source applications |
19 |
| -
|
20 |
| -<p align="left"> |
21 |
| -<a href="https://github.com/sindresorhus/awesome"><img alt="Awesome" src="https://cdn.rawgit.com/sindresorhus/awesome/d7305f38d29fed78fa85652e3a63e154dd8e8829/media/badge.svg" /></a> |
22 |
| -<a href="https://gitter.im/open-source-mac-os-apps/Lobby?utm_source=share-link&utm_medium=link&utm_campaign=share-link"><img alt="Join the chat at gitter" src="https://badges.gitter.im/Join%20Chat.svg" /></a> |
| 28 | + <a href="#audio">Audio</a> • |
| 29 | + <a href="#backup">Backup</a> • |
| 30 | + <a href="#browser">Browser</a> • |
| 31 | + <a href="#chat">Chat</a> • |
| 32 | + <a href="#cryptocurrency">Crypto</a> • |
| 33 | + <a href="#database">Database</a> • |
| 34 | + <a href="#development">Dev</a> • |
| 35 | + <a href="#editors">Editors</a> • |
| 36 | + <a href="#graphics">Graphics</a> • |
| 37 | + <a href="#productivity">Productivity</a> • |
| 38 | + <a href="#utilities">Utilities</a> |
23 | 39 | </p>
|
24 | 40 |
|
25 | 41 | List of awesome open source applications for macOS. This list contains a lot of native, and cross-platform apps. The main goal of this repository is to find free open source apps and start contributing. Feel free to [contribute](CONTRIBUTING.md) to the list, any suggestions are welcome!
|
@@ -109,6 +125,8 @@ You can see in which language an app is written. Currently there are following l
|
109 | 125 |
|
110 | 126 | let footer = """
|
111 | 127 |
|
| 128 | +<div align="right"><a href="#contents">⬆️ Back to Top</a></div> |
| 129 | +
|
112 | 130 | ## Contributors
|
113 | 131 |
|
114 | 132 | Thanks to all the people who contribute:
|
@@ -248,27 +266,43 @@ class ReadmeGenerator {
|
248 | 266 | print("Start iteration....")
|
249 | 267 |
|
250 | 268 | for category in categories {
|
251 |
| - readmeString.append(String.enter + String.section + String.space + category.title + String.enter) |
252 |
| - var categoryApplications = applications.filter({ $0.categories.contains(category.id) }) |
| 269 | + // Add category header with emoji and count |
| 270 | + let categoryApps = applications.filter({ $0.categories.contains(category.id) }) |
| 271 | + let categoryCount = categoryApps.count |
| 272 | + let categoryEmoji = getCategoryEmoji(category.id) |
| 273 | + readmeString.append(String.enter + String.section + String.space + categoryEmoji + String.space + category.title + String.space + "(\(categoryCount))" + String.enter) |
| 274 | + |
| 275 | + var categoryApplications = categoryApps |
253 | 276 | categoryApplications = categoryApplications.sorted(by: { $0.title < $1.title })
|
254 | 277 |
|
255 | 278 | for application in categoryApplications {
|
256 | 279 | readmeString.append(application.markdownDescription())
|
257 | 280 | readmeString.append(String.enter)
|
258 | 281 | }
|
259 | 282 |
|
| 283 | + // Add "Back to Top" link at the end of each category |
| 284 | + readmeString.append("<div align=\"right\"><a href=\"#contents\">⬆️ Back to Top</a></div>" + String.enter) |
| 285 | + |
260 | 286 | var subcategories = subcategories.filter({ $0.parent == category.id })
|
261 | 287 | guard subcategories.count > 0 else { continue }
|
262 | 288 | subcategories = subcategories.sorted(by: { $0.title < $1.title })
|
263 | 289 | for subcategory in subcategories {
|
264 |
| - readmeString.append(String.enter + String.subsection + String.space + subcategory.title + String.enter) |
265 |
| - var categoryApplications = applications.filter({ $0.categories.contains(subcategory.id) }) |
| 290 | + // Add subcategory header with emoji and count |
| 291 | + let subcategoryApps = applications.filter({ $0.categories.contains(subcategory.id) }) |
| 292 | + let subcategoryCount = subcategoryApps.count |
| 293 | + let subcategoryEmoji = getCategoryEmoji(subcategory.id) |
| 294 | + readmeString.append(String.enter + String.subsection + String.space + subcategoryEmoji + String.space + subcategory.title + String.space + "(\(subcategoryCount))" + String.enter) |
| 295 | + |
| 296 | + var categoryApplications = subcategoryApps |
266 | 297 | categoryApplications = categoryApplications.sorted(by: { $0.title < $1.title })
|
267 | 298 |
|
268 | 299 | for application in categoryApplications {
|
269 | 300 | readmeString.append(application.markdownDescription())
|
270 | 301 | readmeString.append(String.enter)
|
271 | 302 | }
|
| 303 | + |
| 304 | + // Add "Back to Top" link at the end of each subcategory |
| 305 | + readmeString.append("<div align=\"right\"><a href=\"#contents\">⬆️ Back to Top</a></div>" + String.enter) |
272 | 306 | }
|
273 | 307 | }
|
274 | 308 | print("Finish iteration...")
|
@@ -298,22 +332,82 @@ extension JSONApplication {
|
298 | 332 | languages.append("![\(lang)\(String.iconPrefix)] ")
|
299 | 333 | }
|
300 | 334 |
|
301 |
| - markdownDescription.append("- [\(self.title)](\(self.repoURL)) - \(self.shortDescription) \(languages)") |
302 |
| - /* |
303 |
| - if self.screenshots.count > 0 { |
304 |
| - var screenshotsString = String.empty |
305 |
| - screenshotsString += (String.space + Constants.detailsBeginString + String.space) |
306 |
| - self.screenshots.forEach({ |
307 |
| - screenshotsString += (String.space + (NSString(format: Constants.srcLinePattern as NSString, $0 as CVarArg) as String) + String.space) |
308 |
| - }) |
309 |
| - screenshotsString += (String.space + Constants.detailsEndString + String.space) |
310 |
| - markdownDescription.append(screenshotsString) |
311 |
| - } |
312 |
| - */ |
| 335 | + // Create a collapsible section for each application |
| 336 | + markdownDescription.append("<details>") |
| 337 | + markdownDescription.append("<summary><b>[\(self.title)](\(self.repoURL))</b> - \(self.shortDescription)</summary>") |
| 338 | + markdownDescription.append("<p>") |
| 339 | + |
| 340 | + // Add languages |
| 341 | + markdownDescription.append("<b>Languages:</b> \(languages)<br>") |
| 342 | + |
| 343 | + // Add official site if available |
| 344 | + if !self.officialSite.isEmpty { |
| 345 | + markdownDescription.append("<b>Website:</b> <a href=\"\(self.officialSite)\">\(self.officialSite)</a><br>") |
| 346 | + } |
| 347 | + |
| 348 | + // Add screenshots with lazy loading to improve page load performance |
| 349 | + if self.screenshots.count > 0 { |
| 350 | + markdownDescription.append("<b>Screenshots:</b><br>") |
| 351 | + |
| 352 | + // Limit to first 3 screenshots to reduce load time |
| 353 | + let limitedScreenshots = self.screenshots.count > 3 ? Array(self.screenshots.prefix(3)) : self.screenshots |
| 354 | + |
| 355 | + limitedScreenshots.forEach({ |
| 356 | + markdownDescription.append("<img src='\($0)' width='400' loading='lazy'/><br>") |
| 357 | + }) |
| 358 | + |
| 359 | + // Add a note if there are more screenshots |
| 360 | + if self.screenshots.count > 3 { |
| 361 | + markdownDescription.append("<em>(\(self.screenshots.count - 3) more screenshots available in the repository)</em><br>") |
| 362 | + } |
| 363 | + } |
| 364 | + |
| 365 | + markdownDescription.append("</p>") |
| 366 | + markdownDescription.append("</details>") |
| 367 | + |
313 | 368 | return markdownDescription
|
314 | 369 | }
|
315 | 370 | }
|
316 | 371 |
|
| 372 | +// Helper function to get emoji for categories |
| 373 | +func getCategoryEmoji(_ categoryId: String) -> String { |
| 374 | + switch categoryId { |
| 375 | + case "audio": return "🎵" |
| 376 | + case "backup": return "💾" |
| 377 | + case "browser": return "🌐" |
| 378 | + case "chat": return "💬" |
| 379 | + case "cryptocurrency": return "💰" |
| 380 | + case "database": return "🗄️" |
| 381 | + case "development": return "👨💻" |
| 382 | + case "downloader": return "⬇️" |
| 383 | + case "editors": return "📝" |
| 384 | + case "extensions": return "🧩" |
| 385 | + case "finder": return "🔍" |
| 386 | + case "games": return "🎮" |
| 387 | + case "graphics": return "🎨" |
| 388 | + case "ide": return "💻" |
| 389 | + case "images": return "🖼️" |
| 390 | + case "keyboard": return "⌨️" |
| 391 | + case "mail": return "📧" |
| 392 | + case "menubar": return "📊" |
| 393 | + case "music": return "🎧" |
| 394 | + case "news": return "📰" |
| 395 | + case "notes": return "📔" |
| 396 | + case "productivity": return "⏱️" |
| 397 | + case "security": return "🔒" |
| 398 | + case "sharing-files": return "📤" |
| 399 | + case "social-networking": return "👥" |
| 400 | + case "system": return "⚙️" |
| 401 | + case "terminal": return "📺" |
| 402 | + case "utilities": return "🛠️" |
| 403 | + case "video": return "🎬" |
| 404 | + case "vpn--proxy": return "🔐" |
| 405 | + case "wallpaper": return "🖥️" |
| 406 | + case "window-management": return "🪟" |
| 407 | + default: return "📦" |
| 408 | + } |
| 409 | +} |
| 410 | + |
317 | 411 | enum FilePaths: String {
|
318 | 412 | case readme = "./README.md"
|
319 | 413 | case applications = "./applications.json"
|
|
0 commit comments