Skip to content

Commit 0b770a4

Browse files
Appcast Link To Live Release Notes (#1809)
* Appcast Link To "Raw" Release Notes * Create Website Deploy Action * Make Linter Happy * Finalize Script * Use new url * Enable JS In Release Notes Alert
1 parent 451d4c2 commit 0b770a4

File tree

7 files changed

+299
-259
lines changed

7 files changed

+299
-259
lines changed
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
on:
2+
release:
3+
types: [created, edited, deleted]
4+
5+
jobs:
6+
deploy-vercel:
7+
name: Trigger A Deploy On Vercel
8+
runs-on: [self-hosted, macOS]
9+
steps:
10+
- name: Make Curl Request
11+
run: curl -X POST ${{ secrets.VERCEL_DEPLOY_URL }}

.github/workflows/pre-release.yml

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,21 +117,27 @@ jobs:
117117
# SPARKLE_CHANNEL: Seperate dev builds from default channel, to be specified in [SPUUpdaterDelegate allowedChannelsForUpdater:]
118118
# SPARKLE_DL_PREFIX: Prefix for the URL from where updates will be downloaded
119119
# SPARKLE_LINK: CodeEdit Website
120-
# https://github.com/CodeEditApp/CodeEdit/releases/download/0.0.1-alpha.11/CodeEdit-9113dc5.dmg
120+
# https://github.com/CodeEditApp/CodeEdit/releases/download/0.0.1-alpha.11/CodeEdit-9113dc5.dmg
121+
# RELEASE_NOTES_PREFIX: The URL to prefix before an update link:
122+
# https://codeedit.app/whats-new/raw/{v0.1.0} -- data in {} is inserted by sparkle
123+
# RELEASE_NOTES_URL: The URL of the entire release notes page: https://codeedit.app/whats-new
121124
SPARKLE_KEY: ${{ secrets.SPARKLE_KEY }}
122125
SPARKLE_CHANNEL: dev
123126
SPARKLE_DL_PREFIX: "https://github.com/CodeEditApp/CodeEdit/releases/download"
124127
SPARKLE_LINK: "https://github.com/CodeEditApp/CodeEdit"
125128
APP_VERSION: ${{ env.APP_VERSION }}
126129
APP_BUILD: ${{ env.APP_BUILD }}
130+
RELEASE_NOTES_URL: "https://codeedit.app/whats-new/"
131+
RELEASE_NOTES_PREFIX: "https://codeedit.app/sparkle/"
127132
run: |
128133
SPARKLE_BIN="$RUNNER_TEMP/DerivedData/SourcePackages/artifacts/sparkle/Sparkle/bin"
129134
SPARKLE_ARCHIVE="$RUNNER_TEMP/Sparkle_Archive"
130135
echo -n "$SPARKLE_KEY" | tee "$RUNNER_TEMP/sparkle_key"
131136
mkdir "$SPARKLE_ARCHIVE"
132137
cp "$RUNNER_TEMP/CodeEdit.dmg" "$SPARKLE_ARCHIVE"
133138
SPARKLE_SIG=$("$SPARKLE_BIN/sign_update" --ed-key-file "$RUNNER_TEMP/sparkle_key" "$SPARKLE_ARCHIVE/CodeEdit.dmg" | cut -d\" -f2)
134-
"$SPARKLE_BIN/generate_appcast" --ed-key-file "$RUNNER_TEMP/sparkle_key" --download-url-prefix "${{ env.SPARKLE_DL_PREFIX }}/v${{ env.APP_VERSION }}/" --link "$SPARKLE_LINK" --channel "$SPARKLE_CHANNEL" --maximum-deltas 0 "$SPARKLE_ARCHIVE"
139+
echo "<\!DOCTYPE>" > "$SPARKLE_ARCHIVE/CodeEdit.html" # Need a blank html doc with the DOCTYPE tag to trick sparkle into loading our remote release notes.
140+
"$SPARKLE_BIN/generate_appcast" --ed-key-file "$RUNNER_TEMP/sparkle_key" --download-url-prefix "${{ env.SPARKLE_DL_PREFIX }}/v${{ env.APP_VERSION }}/" --link "$SPARKLE_LINK" --channel "$SPARKLE_CHANNEL" --maximum-deltas 0 "$SPARKLE_ARCHIVE" --release-notes-url-prefix "${{ env.RELEASE_NOTES_PREFIX }}v${{ env.APP_VERSION }}/" --full-release-notes-url "$RELEASE_NOTES_URL"
135141
136142
############################
137143
# Publish Pre Release

CodeEdit.xcodeproj/project.pbxproj

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,8 @@
384384
6C5FDF7A29E6160000BC08C0 /* AppSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C5FDF7929E6160000BC08C0 /* AppSettings.swift */; };
385385
6C6362D42C3E321A0025570D /* Editor+History.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C6362D32C3E321A0025570D /* Editor+History.swift */; };
386386
6C66C31329D05CDC00DE9ED2 /* GRDB in Frameworks */ = {isa = PBXBuildFile; productRef = 6C66C31229D05CDC00DE9ED2 /* GRDB */; };
387+
6C67413E2C44A28C00AABDF5 /* ProjectNavigatorViewController+DataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C67413D2C44A28C00AABDF5 /* ProjectNavigatorViewController+DataSource.swift */; };
388+
6C6741402C44A2A200AABDF5 /* ProjectNavigatorViewController+Delegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C67413F2C44A2A200AABDF5 /* ProjectNavigatorViewController+Delegate.swift */; };
387389
6C6BD6EF29CD12E900235D17 /* ExtensionManagerWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C6BD6EE29CD12E900235D17 /* ExtensionManagerWindow.swift */; };
388390
6C6BD6F129CD13FA00235D17 /* ExtensionDiscovery.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C6BD6F029CD13FA00235D17 /* ExtensionDiscovery.swift */; };
389391
6C6BD6F429CD142C00235D17 /* CollectionConcurrencyKit in Frameworks */ = {isa = PBXBuildFile; productRef = 6C6BD6F329CD142C00235D17 /* CollectionConcurrencyKit */; };
@@ -1007,6 +1009,8 @@
10071009
6C5C891A2A3F736500A94FE1 /* FocusedValues.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FocusedValues.swift; sourceTree = "<group>"; };
10081010
6C5FDF7929E6160000BC08C0 /* AppSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSettings.swift; sourceTree = "<group>"; };
10091011
6C6362D32C3E321A0025570D /* Editor+History.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Editor+History.swift"; sourceTree = "<group>"; };
1012+
6C67413D2C44A28C00AABDF5 /* ProjectNavigatorViewController+DataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProjectNavigatorViewController+DataSource.swift"; sourceTree = "<group>"; };
1013+
6C67413F2C44A2A200AABDF5 /* ProjectNavigatorViewController+Delegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProjectNavigatorViewController+Delegate.swift"; sourceTree = "<group>"; };
10101014
6C6BD6EE29CD12E900235D17 /* ExtensionManagerWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionManagerWindow.swift; sourceTree = "<group>"; };
10111015
6C6BD6F029CD13FA00235D17 /* ExtensionDiscovery.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionDiscovery.swift; sourceTree = "<group>"; };
10121016
6C6BD6F529CD145F00235D17 /* ExtensionInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionInfo.swift; sourceTree = "<group>"; };
@@ -1368,6 +1372,8 @@
13681372
children = (
13691373
2847019D27FDDF7600F87B6B /* ProjectNavigatorOutlineView.swift */,
13701374
285FEC6D27FE4B4A00E57D53 /* ProjectNavigatorViewController.swift */,
1375+
6C67413D2C44A28C00AABDF5 /* ProjectNavigatorViewController+DataSource.swift */,
1376+
6C67413F2C44A2A200AABDF5 /* ProjectNavigatorViewController+Delegate.swift */,
13711377
285FEC6F27FE4B9800E57D53 /* ProjectNavigatorTableViewCell.swift */,
13721378
285FEC7127FE4EEF00E57D53 /* ProjectNavigatorMenu.swift */,
13731379
D7DC4B75298FFBE900D6C83D /* ProjectNavigatorViewController+OutlineTableViewCellDelegate.swift */,
@@ -3709,6 +3715,7 @@
37093715
58798237292E30B90085B254 /* FeedbackView.swift in Sources */,
37103716
587B9E9829301D8F00AC7927 /* GitCommit.swift in Sources */,
37113717
6C5228B529A868BD00AC48F6 /* Environment+ContentInsets.swift in Sources */,
3718+
6C67413E2C44A28C00AABDF5 /* ProjectNavigatorViewController+DataSource.swift in Sources */,
37123719
587B9E9429301D8F00AC7927 /* BitBucketTokenConfiguration.swift in Sources */,
37133720
04BA7C222AE2D95E00584E1C /* GitClient+CommitHistory.swift in Sources */,
37143721
581BFB672926431000D251EC /* WelcomeWindowView.swift in Sources */,
@@ -4062,6 +4069,7 @@
40624069
77A01E302BB4270F00F0EA38 /* ProjectCEWorkspaceSettingsView.swift in Sources */,
40634070
669A50532C380C8E00304CD8 /* Collection+subscript_safe.swift in Sources */,
40644071
77A01E2C2BB425B200F0EA38 /* CEWorkspaceSettingsData+TasksSettings.swift in Sources */,
4072+
6C6741402C44A2A200AABDF5 /* ProjectNavigatorViewController+Delegate.swift in Sources */,
40654073
5B241BF32B6DDBFF0016E616 /* IgnorePatternListItemView.swift in Sources */,
40664074
6CB52DC92AC8DC3E002E75B3 /* CEWorkspaceFileManager+FileManagement.swift in Sources */,
40674075
58F2EB0B292FB2B0004A9BDE /* AccountsSettings.swift in Sources */,
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
//
2+
// ProjectNavigatorViewController+DataSource.swift
3+
// CodeEdit
4+
//
5+
// Created by Khan Winter on 7/14/24.
6+
//
7+
8+
import AppKit
9+
10+
extension ProjectNavigatorViewController: NSOutlineViewDataSource {
11+
func outlineView(_ outlineView: NSOutlineView, numberOfChildrenOfItem item: Any?) -> Int {
12+
if let item = item as? CEWorkspaceFile {
13+
return item.isFolder ? workspace?.workspaceFileManager?.childrenOfFile(item)?.count ?? 0 : 0
14+
}
15+
return content.count
16+
}
17+
18+
func outlineView(_ outlineView: NSOutlineView, child index: Int, ofItem item: Any?) -> Any {
19+
if let item = item as? CEWorkspaceFile,
20+
let children = workspace?.workspaceFileManager?.childrenOfFile(item) {
21+
return children[index]
22+
}
23+
return content[index]
24+
}
25+
26+
func outlineView(_ outlineView: NSOutlineView, isItemExpandable item: Any) -> Bool {
27+
if let item = item as? CEWorkspaceFile {
28+
return item.isFolder
29+
}
30+
return false
31+
}
32+
33+
/// write dragged file(s) to pasteboard
34+
func outlineView(_ outlineView: NSOutlineView, pasteboardWriterForItem item: Any) -> NSPasteboardWriting? {
35+
guard let fileItem = item as? CEWorkspaceFile else { return nil }
36+
return fileItem.url as NSURL
37+
}
38+
39+
/// declare valid drop target
40+
func outlineView(
41+
_ outlineView: NSOutlineView,
42+
validateDrop info: NSDraggingInfo,
43+
proposedItem item: Any?,
44+
proposedChildIndex index: Int
45+
) -> NSDragOperation {
46+
guard let fileItem = item as? CEWorkspaceFile else { return [] }
47+
// -1 index indicates that we are hovering over a row in outline view (folder or file)
48+
if index == -1 {
49+
if !fileItem.isFolder {
50+
outlineView.setDropItem(fileItem.parent, dropChildIndex: index)
51+
}
52+
return info.draggingSourceOperationMask == .copy ? .copy : .move
53+
}
54+
return []
55+
}
56+
57+
/// handle successful or unsuccessful drop
58+
func outlineView(
59+
_ outlineView: NSOutlineView,
60+
acceptDrop info: NSDraggingInfo,
61+
item: Any?,
62+
childIndex index: Int
63+
) -> Bool {
64+
guard let pasteboardItems = info.draggingPasteboard.readObjects(forClasses: [NSURL.self]) else { return false }
65+
let fileItemURLS = pasteboardItems.compactMap { $0 as? URL }
66+
67+
guard let fileItemDestination = item as? CEWorkspaceFile else { return false }
68+
let destParentURL = fileItemDestination.url
69+
70+
for fileItemURL in fileItemURLS {
71+
let destURL = destParentURL.appendingPathComponent(fileItemURL.lastPathComponent)
72+
// cancel dropping file item on self or in parent directory
73+
if fileItemURL == destURL || fileItemURL == destParentURL {
74+
return false
75+
}
76+
77+
// Needs to come before call to .removeItem or else race condition occurs
78+
var srcFileItem: CEWorkspaceFile? = workspace?.workspaceFileManager?.getFile(fileItemURL.path)
79+
// If srcFileItem is nil, fileItemUrl is an external file url.
80+
if srcFileItem == nil {
81+
srcFileItem = CEWorkspaceFile(url: URL(fileURLWithPath: fileItemURL.path))
82+
}
83+
84+
guard let srcFileItem else {
85+
return false
86+
}
87+
88+
if CEWorkspaceFile.fileManager.fileExists(atPath: destURL.path) {
89+
let shouldReplace = replaceFileDialog(fileName: fileItemURL.lastPathComponent)
90+
guard shouldReplace else {
91+
return false
92+
}
93+
do {
94+
try CEWorkspaceFile.fileManager.removeItem(at: destURL)
95+
} catch {
96+
fatalError(error.localizedDescription)
97+
}
98+
}
99+
if info.draggingSourceOperationMask == .copy {
100+
self.copyFile(file: srcFileItem, to: destURL)
101+
} else {
102+
self.moveFile(file: srcFileItem, to: destURL)
103+
}
104+
}
105+
return true
106+
}
107+
108+
func replaceFileDialog(fileName: String) -> Bool {
109+
let alert = NSAlert()
110+
alert.messageText = """
111+
A file or folder with the name \(fileName) already exists in the destination folder. Do you want to replace it?
112+
"""
113+
alert.informativeText = "This action is irreversible!"
114+
alert.alertStyle = .warning
115+
alert.addButton(withTitle: "Replace")
116+
alert.addButton(withTitle: "Cancel")
117+
return alert.runModal() == .alertFirstButtonReturn
118+
}
119+
}
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
//
2+
// ProjectNavigatorViewController+Delegate.swift
3+
// CodeEdit
4+
//
5+
// Created by Khan Winter on 7/14/24.
6+
//
7+
8+
import AppKit
9+
10+
extension ProjectNavigatorViewController: NSOutlineViewDelegate {
11+
func outlineView(
12+
_ outlineView: NSOutlineView,
13+
shouldShowCellExpansionFor tableColumn: NSTableColumn?,
14+
item: Any
15+
) -> Bool {
16+
true
17+
}
18+
19+
func outlineView(_ outlineView: NSOutlineView, shouldShowOutlineCellForItem item: Any) -> Bool {
20+
true
21+
}
22+
23+
func outlineView(_ outlineView: NSOutlineView, viewFor tableColumn: NSTableColumn?, item: Any) -> NSView? {
24+
guard let tableColumn else { return nil }
25+
26+
let frameRect = NSRect(x: 0, y: 0, width: tableColumn.width, height: rowHeight)
27+
28+
return ProjectNavigatorTableViewCell(frame: frameRect, item: item as? CEWorkspaceFile, delegate: self)
29+
}
30+
31+
func outlineViewSelectionDidChange(_ notification: Notification) {
32+
guard let outlineView = notification.object as? NSOutlineView else { return }
33+
34+
let selectedIndex = outlineView.selectedRow
35+
36+
guard let item = outlineView.item(atRow: selectedIndex) as? CEWorkspaceFile else { return }
37+
38+
if !item.isFolder && shouldSendSelectionUpdate {
39+
DispatchQueue.main.async {
40+
self.shouldSendSelectionUpdate = false
41+
self.workspace?.editorManager.activeEditor.openTab(file: item, asTemporary: true)
42+
self.shouldSendSelectionUpdate = true
43+
}
44+
}
45+
}
46+
47+
func outlineView(_ outlineView: NSOutlineView, heightOfRowByItem item: Any) -> CGFloat {
48+
rowHeight // This can be changed to 20 to match Xcode's row height.
49+
}
50+
51+
func outlineViewItemDidExpand(_ notification: Notification) {
52+
guard let id = workspace?.editorManager.activeEditor.selectedTab?.file.id,
53+
let item = workspace?.workspaceFileManager?.getFile(id, createIfNotFound: true),
54+
/// update outline selection only if the parent of selected item match with expanded item
55+
item.parent === notification.userInfo?["NSObject"] as? CEWorkspaceFile else {
56+
return
57+
}
58+
/// select active file under collapsed folder only if its parent is expanding
59+
if outlineView.isItemExpanded(item.parent) {
60+
updateSelection(itemID: item.id)
61+
}
62+
}
63+
64+
func outlineViewItemDidCollapse(_ notification: Notification) {}
65+
66+
func outlineView(_ outlineView: NSOutlineView, itemForPersistentObject object: Any) -> Any? {
67+
guard let id = object as? CEWorkspaceFile.ID,
68+
let item = workspace?.workspaceFileManager?.getFile(id, createIfNotFound: true) else { return nil }
69+
return item
70+
}
71+
72+
func outlineView(_ outlineView: NSOutlineView, persistentObjectForItem item: Any?) -> Any? {
73+
guard let item = item as? CEWorkspaceFile else { return nil }
74+
return item.id
75+
}
76+
77+
/// Finds and selects an ``Item`` from an array of ``Item`` and their `children` based on the `id`.
78+
/// - Parameters:
79+
/// - id: the id of the item item
80+
/// - collection: the array to search for
81+
/// - forcesReveal: The boolean to indicates whether or not it should force to reveal the selected file.
82+
func select(by id: EditorTabID, forcesReveal: Bool) {
83+
guard case .codeEditor(let path) = id,
84+
let item = workspace?.workspaceFileManager?.getFile(path, createIfNotFound: true) else {
85+
return
86+
}
87+
// If the user has set "Reveal file on selection change" to on or it is forced to reveal,
88+
// we need to reveal the item before selecting the row.
89+
if Settings.shared.preferences.general.revealFileOnFocusChange || forcesReveal {
90+
reveal(item)
91+
}
92+
let row = outlineView.row(forItem: item)
93+
if row == -1 {
94+
outlineView.deselectRow(outlineView.selectedRow)
95+
}
96+
shouldSendSelectionUpdate = false
97+
outlineView.selectRowIndexes(.init(integer: row), byExtendingSelection: false)
98+
shouldSendSelectionUpdate = true
99+
}
100+
101+
/// Reveals the given `fileItem` in the outline view by expanding all the parent directories of the file.
102+
/// If the file is not found, it will present an alert saying so.
103+
/// - Parameter fileItem: The file to reveal.
104+
public func reveal(_ fileItem: CEWorkspaceFile) {
105+
if let parent = fileItem.parent {
106+
expandParent(item: parent)
107+
}
108+
let row = outlineView.row(forItem: fileItem)
109+
shouldSendSelectionUpdate = false
110+
outlineView.selectRowIndexes(.init(integer: row), byExtendingSelection: false)
111+
shouldSendSelectionUpdate = true
112+
113+
if row < 0 {
114+
let alert = NSAlert()
115+
alert.messageText = NSLocalizedString(
116+
"Could not find file",
117+
comment: "Could not find file"
118+
)
119+
alert.runModal()
120+
return
121+
} else {
122+
let visibleRect = scrollView.contentView.visibleRect
123+
let visibleRows = outlineView.rows(in: visibleRect)
124+
guard !visibleRows.contains(row) else {
125+
/// in case that the selected file is not fully visible (some parts are out of the visible rect),
126+
/// `scrollRowToVisible(_:)` method brings the file where it can be fully visible.
127+
outlineView.scrollRowToVisible(row)
128+
return
129+
}
130+
let rowRect = outlineView.rect(ofRow: row)
131+
let centerY = rowRect.midY - (visibleRect.height / 2)
132+
let center = NSPoint(x: 0, y: centerY)
133+
/// `scroll(_:)` method alone doesn't bring the selected file to the center in some cases.
134+
/// calling `scrollRowToVisible(_:)` method before it makes the file reveal in the center more correctly.
135+
outlineView.scrollRowToVisible(row)
136+
outlineView.scroll(center)
137+
}
138+
}
139+
140+
/// Method for recursively expanding a file's parent directories.
141+
/// - Parameter item:
142+
private func expandParent(item: CEWorkspaceFile) {
143+
if let parent = item.parent as CEWorkspaceFile? {
144+
expandParent(item: parent)
145+
}
146+
outlineView.expandItem(item)
147+
}
148+
}

0 commit comments

Comments
 (0)