Add Markdown file previews
This commit is contained in:
@@ -14,6 +14,7 @@
|
|||||||
950C584D58E80106DF350A22 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C0921C76676A14A024BA417 /* AppDelegate.swift */; };
|
950C584D58E80106DF350A22 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C0921C76676A14A024BA417 /* AppDelegate.swift */; };
|
||||||
96C4AFC206A1DBAE3D3E00CE /* SettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F145F13B8ED83A5AB0009A4 /* SettingsViewController.swift */; };
|
96C4AFC206A1DBAE3D3E00CE /* SettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F145F13B8ED83A5AB0009A4 /* SettingsViewController.swift */; };
|
||||||
D59D3ED36ABCC1D8690A9088 /* gotcha_core.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE12480725C13293FAFDDA52 /* gotcha_core.swift */; };
|
D59D3ED36ABCC1D8690A9088 /* gotcha_core.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE12480725C13293FAFDDA52 /* gotcha_core.swift */; };
|
||||||
|
D78A121E9ADA0B72E2CF94DC /* MarkdownUI in Frameworks */ = {isa = PBXBuildFile; productRef = 8AD463F3AC817A44306C2297 /* MarkdownUI */; };
|
||||||
E7AC0B140F5CFC5EF226D174 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DDAABE6B13ADC6D08D9438AF /* Assets.xcassets */; };
|
E7AC0B140F5CFC5EF226D174 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DDAABE6B13ADC6D08D9438AF /* Assets.xcassets */; };
|
||||||
EC7F8B4703DDE37A0B10CD9B /* AppContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = F61515849F6AACD721FE915C /* AppContext.swift */; };
|
EC7F8B4703DDE37A0B10CD9B /* AppContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = F61515849F6AACD721FE915C /* AppContext.swift */; };
|
||||||
F55A89489B2758D694F3B27D /* Support.swift in Sources */ = {isa = PBXBuildFile; fileRef = F75B3E4FFB9C9992517C4D69 /* Support.swift */; };
|
F55A89489B2758D694F3B27D /* Support.swift in Sources */ = {isa = PBXBuildFile; fileRef = F75B3E4FFB9C9992517C4D69 /* Support.swift */; };
|
||||||
@@ -38,6 +39,7 @@
|
|||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
7DD332583B169B3CA1CA46E0 /* Highlighter in Frameworks */,
|
7DD332583B169B3CA1CA46E0 /* Highlighter in Frameworks */,
|
||||||
|
D78A121E9ADA0B72E2CF94DC /* MarkdownUI in Frameworks */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
@@ -103,6 +105,7 @@
|
|||||||
name = Gotcha;
|
name = Gotcha;
|
||||||
packageProductDependencies = (
|
packageProductDependencies = (
|
||||||
EC5F999F50905E3801E8A71A /* Highlighter */,
|
EC5F999F50905E3801E8A71A /* Highlighter */,
|
||||||
|
8AD463F3AC817A44306C2297 /* MarkdownUI */,
|
||||||
);
|
);
|
||||||
productName = Gotcha;
|
productName = Gotcha;
|
||||||
productReference = 5FB3250A93766966A60A685E /* Gotcha.app */;
|
productReference = 5FB3250A93766966A60A685E /* Gotcha.app */;
|
||||||
@@ -134,6 +137,7 @@
|
|||||||
minimizedProjectReferenceProxies = 1;
|
minimizedProjectReferenceProxies = 1;
|
||||||
packageReferences = (
|
packageReferences = (
|
||||||
85EFB86C5B2FDE8DEAD98A24 /* XCRemoteSwiftPackageReference "HighlighterSwift" */,
|
85EFB86C5B2FDE8DEAD98A24 /* XCRemoteSwiftPackageReference "HighlighterSwift" */,
|
||||||
|
0B96240AE4FB1E82EE0D6B17 /* XCRemoteSwiftPackageReference "swift-markdown-ui" */,
|
||||||
);
|
);
|
||||||
preferredProjectObjectVersion = 77;
|
preferredProjectObjectVersion = 77;
|
||||||
productRefGroup = F059299C038F3CAFCE470831 /* Products */;
|
productRefGroup = F059299C038F3CAFCE470831 /* Products */;
|
||||||
@@ -389,6 +393,14 @@
|
|||||||
/* End XCConfigurationList section */
|
/* End XCConfigurationList section */
|
||||||
|
|
||||||
/* Begin XCRemoteSwiftPackageReference section */
|
/* Begin XCRemoteSwiftPackageReference section */
|
||||||
|
0B96240AE4FB1E82EE0D6B17 /* XCRemoteSwiftPackageReference "swift-markdown-ui" */ = {
|
||||||
|
isa = XCRemoteSwiftPackageReference;
|
||||||
|
repositoryURL = "https://github.com/gonzalezreal/swift-markdown-ui";
|
||||||
|
requirement = {
|
||||||
|
kind = upToNextMajorVersion;
|
||||||
|
minimumVersion = 2.1.0;
|
||||||
|
};
|
||||||
|
};
|
||||||
85EFB86C5B2FDE8DEAD98A24 /* XCRemoteSwiftPackageReference "HighlighterSwift" */ = {
|
85EFB86C5B2FDE8DEAD98A24 /* XCRemoteSwiftPackageReference "HighlighterSwift" */ = {
|
||||||
isa = XCRemoteSwiftPackageReference;
|
isa = XCRemoteSwiftPackageReference;
|
||||||
repositoryURL = "https://github.com/smittytone/HighlighterSwift";
|
repositoryURL = "https://github.com/smittytone/HighlighterSwift";
|
||||||
@@ -400,6 +412,11 @@
|
|||||||
/* End XCRemoteSwiftPackageReference section */
|
/* End XCRemoteSwiftPackageReference section */
|
||||||
|
|
||||||
/* Begin XCSwiftPackageProductDependency section */
|
/* Begin XCSwiftPackageProductDependency section */
|
||||||
|
8AD463F3AC817A44306C2297 /* MarkdownUI */ = {
|
||||||
|
isa = XCSwiftPackageProductDependency;
|
||||||
|
package = 0B96240AE4FB1E82EE0D6B17 /* XCRemoteSwiftPackageReference "swift-markdown-ui" */;
|
||||||
|
productName = MarkdownUI;
|
||||||
|
};
|
||||||
EC5F999F50905E3801E8A71A /* Highlighter */ = {
|
EC5F999F50905E3801E8A71A /* Highlighter */ = {
|
||||||
isa = XCSwiftPackageProductDependency;
|
isa = XCSwiftPackageProductDependency;
|
||||||
package = 85EFB86C5B2FDE8DEAD98A24 /* XCRemoteSwiftPackageReference "HighlighterSwift" */;
|
package = 85EFB86C5B2FDE8DEAD98A24 /* XCRemoteSwiftPackageReference "HighlighterSwift" */;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"originHash" : "00a1c96095892a05bfd089593813422ede18735e452666e3e7a642ebd9048a45",
|
"originHash" : "7a8d1ff34841e518cc21434c2148b0c9b3d7c0d8b436c01ab8711755963dcc94",
|
||||||
"pins" : [
|
"pins" : [
|
||||||
{
|
{
|
||||||
"identity" : "highlighterswift",
|
"identity" : "highlighterswift",
|
||||||
@@ -9,6 +9,33 @@
|
|||||||
"revision" : "fe7aae9c9b31d3b296fd3d2dd575e1a207bb29e0",
|
"revision" : "fe7aae9c9b31d3b296fd3d2dd575e1a207bb29e0",
|
||||||
"version" : "3.1.0"
|
"version" : "3.1.0"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "networkimage",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/gonzalezreal/NetworkImage",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "2849f5323265386e200484b0d0f896e73c3411b9",
|
||||||
|
"version" : "6.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "swift-cmark",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/swiftlang/swift-cmark",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "924936d0427cb25a61169739a7660230bffa6ea6",
|
||||||
|
"version" : "0.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "swift-markdown-ui",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/gonzalezreal/swift-markdown-ui",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "5f613358148239d0292c0cef674a3c2314737f9e",
|
||||||
|
"version" : "2.4.1"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"version" : 3
|
"version" : 3
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import Highlighter
|
import Highlighter
|
||||||
|
import MarkdownUI
|
||||||
import QuickLook
|
import QuickLook
|
||||||
|
import SwiftUI
|
||||||
import UIKit
|
import UIKit
|
||||||
import UniformTypeIdentifiers
|
import UniformTypeIdentifiers
|
||||||
|
|
||||||
@@ -336,6 +338,8 @@ final class RepositoryDirectoryViewController: RefreshingTableViewController {
|
|||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
final class RepositoryFileViewController: UIViewController, QLPreviewControllerDataSource {
|
final class RepositoryFileViewController: UIViewController, QLPreviewControllerDataSource {
|
||||||
|
private enum MarkdownMode: Int { case preview, source }
|
||||||
|
|
||||||
private let context: AppContext
|
private let context: AppContext
|
||||||
private let owner: String
|
private let owner: String
|
||||||
private let repository: String
|
private let repository: String
|
||||||
@@ -343,6 +347,9 @@ final class RepositoryFileViewController: UIViewController, QLPreviewControllerD
|
|||||||
private let spinner = UIActivityIndicatorView(style: .medium)
|
private let spinner = UIActivityIndicatorView(style: .medium)
|
||||||
private var loadingTask: Task<Void, Never>?
|
private var loadingTask: Task<Void, Never>?
|
||||||
private var previewURL: URL?
|
private var previewURL: URL?
|
||||||
|
private var markdownSource: String?
|
||||||
|
private var displayedController: UIViewController?
|
||||||
|
private var displayedView: UIView?
|
||||||
private weak var sourceScrollView: UIScrollView?
|
private weak var sourceScrollView: UIScrollView?
|
||||||
private weak var sourceTextView: UITextView?
|
private weak var sourceTextView: UITextView?
|
||||||
|
|
||||||
@@ -370,7 +377,11 @@ final class RepositoryFileViewController: UIViewController, QLPreviewControllerD
|
|||||||
path: path
|
path: path
|
||||||
)
|
)
|
||||||
if !isMediaFile(path), let source = String(data: data, encoding: .utf8) {
|
if !isMediaFile(path), let source = String(data: data, encoding: .utf8) {
|
||||||
showSource(source)
|
if isMarkdownFile {
|
||||||
|
showMarkdown(source)
|
||||||
|
} else {
|
||||||
|
showSource(source)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
try showQuickLook(data)
|
try showQuickLook(data)
|
||||||
}
|
}
|
||||||
@@ -396,6 +407,10 @@ final class RepositoryFileViewController: UIViewController, QLPreviewControllerD
|
|||||||
|| type.conforms(to: .pdf)
|
|| type.conforms(to: .pdf)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var isMarkdownFile: Bool {
|
||||||
|
["md", "markdown", "mdown", "mkd"].contains((path as NSString).pathExtension.lowercased())
|
||||||
|
}
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
loadingTask?.cancel()
|
loadingTask?.cancel()
|
||||||
if let previewURL { try? FileManager.default.removeItem(at: previewURL) }
|
if let previewURL { try? FileManager.default.removeItem(at: previewURL) }
|
||||||
@@ -409,7 +424,40 @@ final class RepositoryFileViewController: UIViewController, QLPreviewControllerD
|
|||||||
previewURL! as NSURL
|
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) {
|
private func showSource(_ source: String) {
|
||||||
|
removeDisplayedView()
|
||||||
let highlighter = Highlighter()
|
let highlighter = Highlighter()
|
||||||
highlighter?.setTheme(
|
highlighter?.setTheme(
|
||||||
traitCollection.userInterfaceStyle == .dark ? "atom-one-dark" : "atom-one-light"
|
traitCollection.userInterfaceStyle == .dark ? "atom-one-dark" : "atom-one-light"
|
||||||
@@ -445,10 +493,21 @@ final class RepositoryFileViewController: UIViewController, QLPreviewControllerD
|
|||||||
sourceScrollView = scrollView
|
sourceScrollView = scrollView
|
||||||
sourceTextView = textView
|
sourceTextView = textView
|
||||||
scrollView.addSubview(textView)
|
scrollView.addSubview(textView)
|
||||||
|
displayedView = scrollView
|
||||||
view.addSubview(scrollView)
|
view.addSubview(scrollView)
|
||||||
updateSourceLayout()
|
updateSourceLayout()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func removeDisplayedView() {
|
||||||
|
displayedController?.willMove(toParent: nil)
|
||||||
|
displayedView?.removeFromSuperview()
|
||||||
|
displayedController?.removeFromParent()
|
||||||
|
displayedController = nil
|
||||||
|
displayedView = nil
|
||||||
|
sourceScrollView = nil
|
||||||
|
sourceTextView = nil
|
||||||
|
}
|
||||||
|
|
||||||
private func updateSourceLayout() {
|
private func updateSourceLayout() {
|
||||||
guard let scrollView = sourceScrollView, let textView = sourceTextView else { return }
|
guard let scrollView = sourceScrollView, let textView = sourceTextView else { return }
|
||||||
textView.textContainer.widthTracksTextView = false
|
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 {
|
enum DiffSource {
|
||||||
case commit(owner: String, repository: String, sha: String, path: String)
|
case commit(owner: String, repository: String, sha: String, path: String)
|
||||||
case pull(owner: String, repository: String, number: Int64, path: String)
|
case pull(owner: String, repository: String, number: Int64, path: String)
|
||||||
|
|||||||
@@ -7,6 +7,9 @@ packages:
|
|||||||
Highlighter:
|
Highlighter:
|
||||||
url: https://github.com/smittytone/HighlighterSwift
|
url: https://github.com/smittytone/HighlighterSwift
|
||||||
from: 3.1.0
|
from: 3.1.0
|
||||||
|
MarkdownUI:
|
||||||
|
url: https://github.com/gonzalezreal/swift-markdown-ui
|
||||||
|
from: 2.1.0
|
||||||
targets:
|
targets:
|
||||||
Gotcha:
|
Gotcha:
|
||||||
type: application
|
type: application
|
||||||
@@ -35,6 +38,7 @@ targets:
|
|||||||
- Generated/gotcha_core.swift
|
- Generated/gotcha_core.swift
|
||||||
dependencies:
|
dependencies:
|
||||||
- package: Highlighter
|
- package: Highlighter
|
||||||
|
- package: MarkdownUI
|
||||||
preBuildScripts:
|
preBuildScripts:
|
||||||
- name: Build Rust core
|
- name: Build Rust core
|
||||||
basedOnDependencyAnalysis: false
|
basedOnDependencyAnalysis: false
|
||||||
|
|||||||
Reference in New Issue
Block a user