Add Markdown file previews
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
import Highlighter
|
||||
import MarkdownUI
|
||||
import QuickLook
|
||||
import SwiftUI
|
||||
import UIKit
|
||||
import UniformTypeIdentifiers
|
||||
|
||||
@@ -336,6 +338,8 @@ final class RepositoryDirectoryViewController: RefreshingTableViewController {
|
||||
|
||||
@MainActor
|
||||
final class RepositoryFileViewController: UIViewController, QLPreviewControllerDataSource {
|
||||
private enum MarkdownMode: Int { case preview, source }
|
||||
|
||||
private let context: AppContext
|
||||
private let owner: String
|
||||
private let repository: String
|
||||
@@ -343,6 +347,9 @@ final class RepositoryFileViewController: UIViewController, QLPreviewControllerD
|
||||
private let spinner = UIActivityIndicatorView(style: .medium)
|
||||
private var loadingTask: Task<Void, Never>?
|
||||
private var previewURL: URL?
|
||||
private var markdownSource: String?
|
||||
private var displayedController: UIViewController?
|
||||
private var displayedView: UIView?
|
||||
private weak var sourceScrollView: UIScrollView?
|
||||
private weak var sourceTextView: UITextView?
|
||||
|
||||
@@ -370,7 +377,11 @@ final class RepositoryFileViewController: UIViewController, QLPreviewControllerD
|
||||
path: path
|
||||
)
|
||||
if !isMediaFile(path), let source = String(data: data, encoding: .utf8) {
|
||||
showSource(source)
|
||||
if isMarkdownFile {
|
||||
showMarkdown(source)
|
||||
} else {
|
||||
showSource(source)
|
||||
}
|
||||
} else {
|
||||
try showQuickLook(data)
|
||||
}
|
||||
@@ -396,6 +407,10 @@ final class RepositoryFileViewController: UIViewController, QLPreviewControllerD
|
||||
|| type.conforms(to: .pdf)
|
||||
}
|
||||
|
||||
private var isMarkdownFile: Bool {
|
||||
["md", "markdown", "mdown", "mkd"].contains((path as NSString).pathExtension.lowercased())
|
||||
}
|
||||
|
||||
deinit {
|
||||
loadingTask?.cancel()
|
||||
if let previewURL { try? FileManager.default.removeItem(at: previewURL) }
|
||||
@@ -409,7 +424,40 @@ final class RepositoryFileViewController: UIViewController, QLPreviewControllerD
|
||||
previewURL! as NSURL
|
||||
}
|
||||
|
||||
private func showMarkdown(_ source: String) {
|
||||
markdownSource = source
|
||||
navigationItem.prompt = title
|
||||
let modeControl = UISegmentedControl(items: ["Preview", "Source"])
|
||||
modeControl.selectedSegmentIndex = MarkdownMode.preview.rawValue
|
||||
modeControl.addTarget(self, action: #selector(markdownModeChanged(_:)), for: .valueChanged)
|
||||
modeControl.accessibilityLabel = "Markdown view"
|
||||
navigationItem.titleView = modeControl
|
||||
showMarkdownPreview(source)
|
||||
}
|
||||
|
||||
@objc private func markdownModeChanged(_ sender: UISegmentedControl) {
|
||||
guard let mode = MarkdownMode(rawValue: sender.selectedSegmentIndex),
|
||||
let source = markdownSource else { return }
|
||||
switch mode {
|
||||
case .preview: showMarkdownPreview(source)
|
||||
case .source: showSource(source)
|
||||
}
|
||||
}
|
||||
|
||||
private func showMarkdownPreview(_ source: String) {
|
||||
removeDisplayedView()
|
||||
let preview = UIHostingController(rootView: RepositoryMarkdownPreview(source: source))
|
||||
addChild(preview)
|
||||
preview.view.frame = view.bounds
|
||||
preview.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
||||
displayedController = preview
|
||||
displayedView = preview.view
|
||||
view.addSubview(preview.view)
|
||||
preview.didMove(toParent: self)
|
||||
}
|
||||
|
||||
private func showSource(_ source: String) {
|
||||
removeDisplayedView()
|
||||
let highlighter = Highlighter()
|
||||
highlighter?.setTheme(
|
||||
traitCollection.userInterfaceStyle == .dark ? "atom-one-dark" : "atom-one-light"
|
||||
@@ -445,10 +493,21 @@ final class RepositoryFileViewController: UIViewController, QLPreviewControllerD
|
||||
sourceScrollView = scrollView
|
||||
sourceTextView = textView
|
||||
scrollView.addSubview(textView)
|
||||
displayedView = scrollView
|
||||
view.addSubview(scrollView)
|
||||
updateSourceLayout()
|
||||
}
|
||||
|
||||
private func removeDisplayedView() {
|
||||
displayedController?.willMove(toParent: nil)
|
||||
displayedView?.removeFromSuperview()
|
||||
displayedController?.removeFromParent()
|
||||
displayedController = nil
|
||||
displayedView = nil
|
||||
sourceScrollView = nil
|
||||
sourceTextView = nil
|
||||
}
|
||||
|
||||
private func updateSourceLayout() {
|
||||
guard let scrollView = sourceScrollView, let textView = sourceTextView else { return }
|
||||
textView.textContainer.widthTracksTextView = false
|
||||
@@ -488,6 +547,20 @@ final class RepositoryFileViewController: UIViewController, QLPreviewControllerD
|
||||
}
|
||||
}
|
||||
|
||||
private struct RepositoryMarkdownPreview: View {
|
||||
let source: String
|
||||
|
||||
var body: some View {
|
||||
ScrollView {
|
||||
Markdown(source)
|
||||
.markdownTheme(.gitHub)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.padding()
|
||||
}
|
||||
.background(Color(uiColor: .systemBackground))
|
||||
}
|
||||
}
|
||||
|
||||
enum DiffSource {
|
||||
case commit(owner: String, repository: String, sha: String, path: String)
|
||||
case pull(owner: String, repository: String, number: Int64, path: String)
|
||||
|
||||
Reference in New Issue
Block a user