Skip to content

Commit

Permalink
Add Edit URL action
Browse files Browse the repository at this point in the history
  • Loading branch information
sindresorhus committed Nov 18, 2021
1 parent 53b986e commit 073f5ec
Show file tree
Hide file tree
Showing 16 changed files with 1,548 additions and 27 deletions.
14 changes: 14 additions & 0 deletions Actions.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
E3327E97272C68DE00AD5CC7 /* Sentry in Frameworks */ = {isa = PBXBuildFile; productRef = E3327E96272C68DE00AD5CC7 /* Sentry */; };
E3327E99272C79F900AD5CC7 /* RemoveDuplicateLines.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3327E98272C79F900AD5CC7 /* RemoveDuplicateLines.swift */; };
E3327E9B272D22DC00AD5CC7 /* AddToList.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3327E9A272D22DC00AD5CC7 /* AddToList.swift */; };
E33D4BA52744238300CC7A8A /* AppState.swift in Sources */ = {isa = PBXBuildFile; fileRef = E33D4BA42744238300CC7A8A /* AppState.swift */; };
E33D4BA62744238300CC7A8A /* AppState.swift in Sources */ = {isa = PBXBuildFile; fileRef = E33D4BA42744238300CC7A8A /* AppState.swift */; };
E34D03742725BB1A0065FC3B /* Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3FC71DC271EBE5B00C9D255 /* Utilities.swift */; };
E34D03762725BCD10065FC3B /* RandomBoolean.swift in Sources */ = {isa = PBXBuildFile; fileRef = E34D03752725BCD10065FC3B /* RandomBoolean.swift */; };
E34D03782725C1340065FC3B /* RemoveEmojis.swift in Sources */ = {isa = PBXBuildFile; fileRef = E34D03772725C1340065FC3B /* RemoveEmojis.swift */; };
Expand Down Expand Up @@ -101,6 +103,8 @@
E3C6C1A427366A4D0003F56E /* RemoveFromList.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3C6C1A327366A4D0003F56E /* RemoveFromList.swift */; };
E3C6C1A527366A4D0003F56E /* RemoveFromList.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3C6C1A327366A4D0003F56E /* RemoveFromList.swift */; };
E3D3BDC9274367BC00417A63 /* IsConnectedToVPN.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3D3BDC7274367BC00417A63 /* IsConnectedToVPN.swift */; };
E3D3BDCC2743A72C00417A63 /* EditURL.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3D3BDCB2743A72C00417A63 /* EditURL.swift */; };
E3D3BDCD2743A72C00417A63 /* EditURL.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3D3BDCB2743A72C00417A63 /* EditURL.swift */; };
E3DF2EBB27287405009127CA /* TransformTextWithJavaScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3DF2EBA27287405009127CA /* TransformTextWithJavaScript.swift */; };
E3DF2EBD272931C3009127CA /* RandomColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3DF2EBC272931C3009127CA /* RandomColor.swift */; };
E3DF2EBF27293225009127CA /* SampleColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3DF2EBE27293225009127CA /* SampleColor.swift */; };
Expand Down Expand Up @@ -179,6 +183,7 @@
E324CA29271EA67E00E7CA9B /* Config.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Config.xcconfig; sourceTree = "<group>"; };
E3327E98272C79F900AD5CC7 /* RemoveDuplicateLines.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoveDuplicateLines.swift; sourceTree = "<group>"; };
E3327E9A272D22DC00AD5CC7 /* AddToList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddToList.swift; sourceTree = "<group>"; };
E33D4BA42744238300CC7A8A /* AppState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppState.swift; sourceTree = "<group>"; };
E34D03752725BCD10065FC3B /* RandomBoolean.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RandomBoolean.swift; sourceTree = "<group>"; };
E34D03772725C1340065FC3B /* RemoveEmojis.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoveEmojis.swift; sourceTree = "<group>"; };
E34D03792725C2D30065FC3B /* TrimWhitespace.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrimWhitespace.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -211,6 +216,7 @@
E3BFF7B92742C7BD00B830DE /* GetUserDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetUserDetails.swift; sourceTree = "<group>"; };
E3C6C1A327366A4D0003F56E /* RemoveFromList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoveFromList.swift; sourceTree = "<group>"; };
E3D3BDC7274367BC00417A63 /* IsConnectedToVPN.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = IsConnectedToVPN.swift; path = IntentsExtension/Actions/IsConnectedToVPN.swift; sourceTree = SOURCE_ROOT; };
E3D3BDCB2743A72C00417A63 /* EditURL.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditURL.swift; sourceTree = "<group>"; };
E3DF2EBA27287405009127CA /* TransformTextWithJavaScript.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransformTextWithJavaScript.swift; sourceTree = "<group>"; };
E3DF2EBC272931C3009127CA /* RandomColor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RandomColor.swift; sourceTree = "<group>"; };
E3DF2EBE27293225009127CA /* SampleColor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SampleColor.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -290,6 +296,7 @@
isa = PBXGroup;
children = (
E324C9EF271E972200E7CA9B /* App.swift */,
E33D4BA42744238300CC7A8A /* AppState.swift */,
E324C9F0271E972200E7CA9B /* MainScreen.swift */,
E3BFF7B327428F5200B830DE /* WelcomeScreen.swift */,
E3BFF7B62742A79100B830DE /* AppIcon.swift */,
Expand Down Expand Up @@ -386,6 +393,7 @@
E3BFF7AD2742456E00B830DE /* WriteText.swift */,
E3BFF7B92742C7BD00B830DE /* GetUserDetails.swift */,
E3D3BDC7274367BC00417A63 /* IsConnectedToVPN.swift */,
E3D3BDCB2743A72C00417A63 /* EditURL.swift */,
);
path = Actions;
sourceTree = "<group>";
Expand Down Expand Up @@ -583,6 +591,7 @@
E3BFF7B527428F5200B830DE /* WelcomeScreen.swift in Sources */,
E324CA23271E993F00E7CA9B /* Intents.intentdefinition in Sources */,
E324CA01271E972300E7CA9B /* MainScreen.swift in Sources */,
E33D4BA62744238300CC7A8A /* AppState.swift in Sources */,
E3BFF7B22742616400B830DE /* WriteTextScreen.swift in Sources */,
E3BFF7B82742A79100B830DE /* AppIcon.swift in Sources */,
E3FC71DD271EBE5C00C9D255 /* Utilities.swift in Sources */,
Expand All @@ -597,6 +606,7 @@
E3BFF7B427428F5200B830DE /* WelcomeScreen.swift in Sources */,
E324CA2B271EAAAC00E7CA9B /* Intents.intentdefinition in Sources */,
E324CA02271E972300E7CA9B /* MainScreen.swift in Sources */,
E33D4BA52744238300CC7A8A /* AppState.swift in Sources */,
E3BFF7B12742616400B830DE /* WriteTextScreen.swift in Sources */,
E3BFF7B72742A79100B830DE /* AppIcon.swift in Sources */,
E3FC71DE271EBE5C00C9D255 /* Utilities.swift in Sources */,
Expand Down Expand Up @@ -646,6 +656,7 @@
E38FB4E8273EB47B00FF3C9A /* FilterList.swift in Sources */,
E3DF2EBF27293225009127CA /* SampleColor.swift in Sources */,
E38FB4EB273EDCD600FF3C9A /* TruncateList.swift in Sources */,
E3D3BDCC2743A72C00417A63 /* EditURL.swift in Sources */,
E3BFF7BA2742C7BD00B830DE /* GetUserDetails.swift in Sources */,
E3DF2EBD272931C3009127CA /* RandomColor.swift in Sources */,
E3BFF7AB27422F6700B830DE /* IsDarkMode.swift in Sources */,
Expand All @@ -662,6 +673,7 @@
E39F476F272D3DF600B99CB0 /* GetUniformTypeIdentifier.swift in Sources */,
E39F4755272D3DED00B99CB0 /* IntentHandler.swift in Sources */,
E39F476E272D3DF600B99CB0 /* RandomBoolean.swift in Sources */,
E3D3BDCD2743A72C00417A63 /* EditURL.swift in Sources */,
E3EE4041273802FB009B3FED /* ApplyCaptureDate.swift in Sources */,
E3C6C1A527366A4D0003F56E /* RemoveFromList.swift in Sources */,
E39F4760272D3DF600B99CB0 /* RemoveDuplicateLines.swift in Sources */,
Expand Down Expand Up @@ -855,6 +867,7 @@
INFOPLIST_FILE = iOS/Info.plist;
INFOPLIST_KEY_NSAppleMusicUsageDescription = "Required to use the “Get Music Playlists” action.";
INFOPLIST_KEY_NSBluetoothAlwaysUsageDescription = "Required to use the “Is Bluetooth On” action.";
INFOPLIST_KEY_NSBluetoothPeripheralUsageDescription = "Required to use the “Is Bluetooth On” action.";
INFOPLIST_KEY_NSContactsUsageDescription = "Required to get the user's name in the “Get User Details” action.";
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
Expand Down Expand Up @@ -889,6 +902,7 @@
INFOPLIST_FILE = iOS/Info.plist;
INFOPLIST_KEY_NSAppleMusicUsageDescription = "Required to use the “Get Music Playlists” action.";
INFOPLIST_KEY_NSBluetoothAlwaysUsageDescription = "Required to use the “Is Bluetooth On” action.";
INFOPLIST_KEY_NSBluetoothPeripheralUsageDescription = "Required to use the “Is Bluetooth On” action.";
INFOPLIST_KEY_NSContactsUsageDescription = "Required to get the user's name in the “Get User Details” action.";
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
Expand Down
8 changes: 6 additions & 2 deletions IntentsExtension/Actions/CreateURL.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@ final class CreateURLIntentHandler: NSObject, CreateURLIntentHandling {
var urlComponents = URLComponents()

// Setting it to "https:" is not valid, but we gracefully handle that for the user.
urlComponents.scheme = intent.scheme?.replacingSuffix(":", with: "") ?? "https"
urlComponents.scheme = intent.scheme?
.nilIfEmptyOrWhitespace?
.replacingSuffix("://", with: "")
.replacingSuffix(":", with: "")
?? "https"

if let host = intent.host?.nilIfEmptyOrWhitespace {
urlComponents.host = host
Expand Down Expand Up @@ -47,7 +51,7 @@ final class CreateURLIntentHandler: NSObject, CreateURLIntentHandling {
}

guard let url = urlComponents.url else {
return .init(code: .failure, userActivity: nil)
return .failure(failure: "The created URL is invalid.")
}

let response = CreateURLIntentResponse(code: .success, userActivity: nil)
Expand Down
102 changes: 102 additions & 0 deletions IntentsExtension/Actions/EditURL.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import SwiftUI

@MainActor
final class EditURLIntentHandler: NSObject, EditURLIntentHandling {
func handle(intent: EditURLIntent) async -> EditURLIntentResponse {
guard let url = intent.url else {
return .init(code: .success, userActivity: nil)
}

guard var urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: true) else {
return .failure(failure: "\(url)” is not a valid URL.")
}

let response = EditURLIntentResponse(code: .success, userActivity: nil)

switch intent.action {
case .unknown:
return .init(code: .failure, userActivity: nil)
case .addQueryItem:
if
let name = intent.queryItemName?.nilIfEmptyOrWhitespace,
let value = intent.queryItemValue?.nilIfEmpty
{
urlComponents.queryItems = (urlComponents.queryItems ?? [])
.appending(.init(name: name, value: value))
}
case .addPathComponent:
if let pathComponent = intent.addPathComponentValue?.nilIfEmptyOrWhitespace {
response.result = url.appendingPathComponent(pathComponent)
}

return response
case .appendToQuery:
if let queryPart = intent.appendToQueryValue?.nilIfEmptyOrWhitespace {
urlComponents.query = (urlComponents.query ?? "") + queryPart
}
case .appendToFragment:
if let fragmentPart = intent.appendToFragmentValue?.nilIfEmptyOrWhitespace {
urlComponents.fragment = (urlComponents.fragment ?? "") + fragmentPart
}
case .removeQueryItemsNamed:
if let name = intent.removeQueryItemsNamedValue {
urlComponents.queryItems = (urlComponents.queryItems ?? [])
.filter { $0.name != name }
.nilIfEmpty
}
case .removeLastPathComponent:
response.result = url.deletingLastPathComponent()
return response
case .removeQuery:
urlComponents.query = nil
case .removeFragment:
urlComponents.fragment = nil
case .removePath:
urlComponents.path = ""
case .removePort:
urlComponents.port = nil
case .removeUserAndPassword:
urlComponents.user = nil
urlComponents.password = nil
case .setQuery:
if let query = intent.setQueryValue?.nilIfEmptyOrWhitespace {
urlComponents.query = query
}
case .setFragment:
if let fragment = intent.setFragmentValue?.nilIfEmptyOrWhitespace {
urlComponents.fragment = fragment
}
case .setPath:
if let path = intent.setPathValue?.nilIfEmptyOrWhitespace {
// Including the `/` prefix is required, but we handle it so the user does not have to care.
urlComponents.path = path.trimmingCharacters(in: .whitespaces).ensurePrefix("/")
}
case .setScheme:
if let scheme = intent.setSchemeValue?.nilIfEmptyOrWhitespace {
urlComponents.scheme = scheme
.replacingSuffix("://", with: "")
.replacingSuffix(":", with: "")
}
case .setHost:
if let host = intent.setHostValue?.nilIfEmptyOrWhitespace {
urlComponents.host = host
}
case .setUsernameAndPassword:
if
let username = intent.username?.trimmingCharacters(in: .whitespaces).nilIfEmpty,
let password = intent.password?.nilIfEmpty
{
urlComponents.user = username
urlComponents.password = password
}
}

guard let url = urlComponents.url else {
return .failure(failure: "The edited URL is invalid.")
}

response.result = url

return response
}
}
1 change: 1 addition & 0 deletions IntentsExtension/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
<string>CombineListsIntent</string>
<string>CreateURLIntent</string>
<string>DateToUnixTimeIntent</string>
<string>EditURLIntent</string>
<string>FilterListIntent</string>
<string>FormatDateDifferenceIntent</string>
<string>GenerateUUIDIntent</string>
Expand Down
2 changes: 2 additions & 0 deletions IntentsExtension/IntentHandler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@ final class IntentHandler: INExtension {
case is IsConnectedToVPNIntent:
return IsConnectedToVPNIntentHandler()
#endif
case is EditURLIntent:
return EditURLIntentHandler()
default:
assertionFailure("No handler for this intent")
return nil
Expand Down
Loading

0 comments on commit 073f5ec

Please sign in to comment.