aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Info.plist39
-rw-r--r--QSThingsPlugin.xcodeproj/project.pbxproj4
-rw-r--r--QSThingsPlugin/Actions/CompleteTaskAction.h12
-rw-r--r--QSThingsPlugin/Actions/CompleteTaskAction.m41
-rw-r--r--QSThingsPlugin/Actions/SetThingsAuthenticationKeyAction.h11
-rw-r--r--QSThingsPlugin/Actions/SetThingsAuthenticationKeyAction.m21
-rw-r--r--QSThingsPlugin/Helpers/KeychainHelper.h20
-rw-r--r--QSThingsPlugin/Helpers/KeychainHelper.m83
-rw-r--r--QSThingsPlugin/Parsers/QSThingsAreasParser.m4
-rw-r--r--QSThingsPlugin/QSThingsPluginSource.m3
-rw-r--r--QSThingsPlugin/QSThingsPluginSource.xib50
-rw-r--r--QSThingsPlugin/Types/Constants.h4
-rw-r--r--QSThingsPlugin/Types/Constants.m4
13 files changed, 241 insertions, 55 deletions
diff --git a/Info.plist b/Info.plist
index 37687c5..b43bf23 100644
--- a/Info.plist
+++ b/Info.plist
@@ -22,6 +22,45 @@
<string>QSThingsPluginSource</string>
<key>QSActions</key>
<dict>
+ <key>CompleteTaskAction</key>
+ <dict>
+ <key>actionClass</key>
+ <string>CompleteTaskAction</string>
+ <key>actionSelector</key>
+ <string>completeTask:</string>
+ <key>directTypes</key>
+ <array>
+ <string>things.project</string>
+ <string>things.task</string>
+ </array>
+ <key>enabled</key>
+ <true/>
+ <key>displaysResult</key>
+ <false/>
+ <key>name</key>
+ <string>Mark as Complete</string>
+ <key>icon</key>
+ <string>com.culturedcode.ThingsMac</string>
+ </dict>
+ <key>SetThingsAuthenticationKeyAction</key>
+ <dict>
+ <key>actionClass</key>
+ <string>SetThingsAuthenticationKeyAction</string>
+ <key>actionSelector</key>
+ <string>setAuthenticationKey:</string>
+ <key>directTypes</key>
+ <array>
+ <string>NSStringPboardType</string>
+ </array>
+ <key>enabled</key>
+ <true/>
+ <key>displaysResult</key>
+ <false/>
+ <key>name</key>
+ <string>Set Things Authentication Key</string>
+ <key>icon</key>
+ <string>com.culturedcode.ThingsMac</string>
+ </dict>
<key>SearchThingsAction</key>
<dict>
<key>actionClass</key>
diff --git a/QSThingsPlugin.xcodeproj/project.pbxproj b/QSThingsPlugin.xcodeproj/project.pbxproj
index 2b09a13..ced85a8 100644
--- a/QSThingsPlugin.xcodeproj/project.pbxproj
+++ b/QSThingsPlugin.xcodeproj/project.pbxproj
@@ -39,14 +39,16 @@
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
membershipExceptions = (
/Localized/Localizable.strings,
+ Actions/CompleteTaskAction.m,
Actions/SearchThingsAction.m,
+ Actions/SetThingsAuthenticationKeyAction.m,
Actions/ShowInThingsAction.m,
+ Helpers/KeychainHelper.m,
Helpers/SQLHelper.m,
Parsers/QSThingsAreasParser.m,
Parsers/QSThingsProjectsParser.m,
Parsers/QSThingsTasksParser.m,
QSThingsPluginSource.m,
- QSThingsPluginSource.xib,
Types/Constants.m,
);
target = 8D1AC9600486D14A00FE50C9 /* Things Plugin */;
diff --git a/QSThingsPlugin/Actions/CompleteTaskAction.h b/QSThingsPlugin/Actions/CompleteTaskAction.h
new file mode 100644
index 0000000..19609b9
--- /dev/null
+++ b/QSThingsPlugin/Actions/CompleteTaskAction.h
@@ -0,0 +1,12 @@
+#import "Constants.h"
+#import "KeychainHelper.h"
+#import <Foundation/Foundation.h>
+#import <QSCore/QSCore.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface CompleteTaskAction : QSActionProvider
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/QSThingsPlugin/Actions/CompleteTaskAction.m b/QSThingsPlugin/Actions/CompleteTaskAction.m
new file mode 100644
index 0000000..9673840
--- /dev/null
+++ b/QSThingsPlugin/Actions/CompleteTaskAction.m
@@ -0,0 +1,41 @@
+#import "CompleteTaskAction.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@implementation CompleteTaskAction
+
+- (QSObject *)completeTask:(QSObject *)directObject
+{
+ NSSet *validTypes = [NSSet setWithObjects:kProjectType, kTaskType, nil];
+ NSString *authenticationKey = [KeychainHelper authenticationKey];
+
+ if (!authenticationKey) {
+ NSLog(@"There is no authentication key.");
+ }
+
+ if (authenticationKey && [validTypes containsObject:directObject.primaryType]) {
+ NSDictionary *contents = [directObject objectForType:directObject.primaryType];
+ NSString *path = @"update";
+ if (directObject.primaryType == kProjectType) {
+ path = @"update-project";
+ }
+ if (contents) {
+ NSString *uuid = [contents objectForKey:@"uuid"];
+ if (uuid) {
+ NSString *urlString = [NSString stringWithFormat:@"things:///%@?id=%@&auth-token=%@&completed=true", path, uuid, authenticationKey];
+ NSURL *url = [NSURL URLWithString:urlString];
+
+ [[NSWorkspace sharedWorkspace] openURL:url];
+ } else {
+ NSLog(@"Things object did not have a uuid.");
+ }
+ } else {
+ NSLog(@"Could not get the contents of the Things object.");
+ }
+ }
+ return directObject;
+}
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/QSThingsPlugin/Actions/SetThingsAuthenticationKeyAction.h b/QSThingsPlugin/Actions/SetThingsAuthenticationKeyAction.h
new file mode 100644
index 0000000..376d6d4
--- /dev/null
+++ b/QSThingsPlugin/Actions/SetThingsAuthenticationKeyAction.h
@@ -0,0 +1,11 @@
+#import "KeychainHelper.h"
+#import <Foundation/Foundation.h>
+#import <QSCore/QSCore.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface SetThingsAuthenticationKeyAction : QSActionProvider
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/QSThingsPlugin/Actions/SetThingsAuthenticationKeyAction.m b/QSThingsPlugin/Actions/SetThingsAuthenticationKeyAction.m
new file mode 100644
index 0000000..b9e7031
--- /dev/null
+++ b/QSThingsPlugin/Actions/SetThingsAuthenticationKeyAction.m
@@ -0,0 +1,21 @@
+#import "SetThingsAuthenticationKeyAction.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@implementation SetThingsAuthenticationKeyAction
+
+- (QSObject *)setAuthenticationKey:(QSObject *)directObject
+{
+
+ NSString *contents = [directObject displayName];
+ if (contents) {
+ [KeychainHelper setAuthenticationKey:contents];
+ } else {
+ [KeychainHelper deleteAuthenticationKey];
+ }
+ return directObject;
+}
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/QSThingsPlugin/Helpers/KeychainHelper.h b/QSThingsPlugin/Helpers/KeychainHelper.h
new file mode 100644
index 0000000..d935bec
--- /dev/null
+++ b/QSThingsPlugin/Helpers/KeychainHelper.h
@@ -0,0 +1,20 @@
+//
+// KeychainHelper.h
+// Things Plugin
+//
+// Created by Ruben Beltran del Rio on 2025-09-10.
+//
+
+#import "Constants.h"
+#import <Foundation/Foundation.h>
+#import <Security/Security.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface KeychainHelper : NSObject
++ (NSString *)authenticationKey;
++ (OSStatus)setAuthenticationKey:(NSString *)authenticationKey;
++ (OSStatus)deleteAuthenticationKey;
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/QSThingsPlugin/Helpers/KeychainHelper.m b/QSThingsPlugin/Helpers/KeychainHelper.m
new file mode 100644
index 0000000..9ce90f7
--- /dev/null
+++ b/QSThingsPlugin/Helpers/KeychainHelper.m
@@ -0,0 +1,83 @@
+//
+// KeychainHelper.m
+// Things Plugin
+//
+// Created by Ruben Beltran del Rio on 2025-09-10.
+//
+
+#import "KeychainHelper.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@implementation KeychainHelper
+
++ (NSString *)authenticationKey {
+ const char *service = [kKeychainService UTF8String];
+ const char *account = [kAuthenticationKeyKeychainKey UTF8String];
+
+ UInt32 authenticationKeyLength = 0;
+ void *authenticationKeyData = NULL;
+
+ OSStatus status = SecKeychainFindGenericPassword(
+ NULL, (UInt32)strlen(service), service, (UInt32)strlen(account), account,
+ &authenticationKeyLength, &authenticationKeyData, NULL);
+
+ if (status == errSecSuccess && authenticationKeyData != NULL) {
+ NSString *authenticationKey = [[NSString alloc] initWithBytes:authenticationKeyData
+ length:authenticationKeyLength
+ encoding:NSUTF8StringEncoding];
+ SecKeychainItemFreeContent(NULL, authenticationKeyData);
+ return authenticationKey;
+ }
+
+ return nil;
+}
+
++ (OSStatus)setAuthenticationKey:(NSString *)authenticationKey {
+ const char *service = [kKeychainService UTF8String];
+ const char *account = [kAuthenticationKeyKeychainKey UTF8String];
+ const char *authenticationKeyCString = [authenticationKey UTF8String];
+
+ // First try to find existing item
+ SecKeychainItemRef item = NULL;
+ OSStatus findStatus = SecKeychainFindGenericPassword(
+ NULL, (UInt32)strlen(service), service, (UInt32)strlen(account), account,
+ NULL, NULL, &item);
+
+ OSStatus status;
+ if (findStatus == errSecSuccess) {
+ // Update existing item
+ status = SecKeychainItemModifyAttributesAndData(
+ item, NULL, (UInt32)strlen(authenticationKeyCString), authenticationKeyCString);
+ CFRelease(item);
+ } else {
+ // Create new item
+ status = SecKeychainAddGenericPassword(
+ NULL, (UInt32)strlen(service), service, (UInt32)strlen(account),
+ account, (UInt32)strlen(authenticationKeyCString), authenticationKeyCString, NULL);
+ }
+
+ return status;
+}
+
++ (OSStatus) deleteAuthenticationKey {
+ const char *service = [kKeychainService UTF8String];
+ const char *account = [kAuthenticationKeyKeychainKey UTF8String];
+
+ SecKeychainItemRef item = NULL;
+ OSStatus findStatus = SecKeychainFindGenericPassword(
+ NULL, (UInt32)strlen(service), service, (UInt32)strlen(account), account,
+ NULL, NULL, &item);
+
+ if (findStatus == errSecSuccess) {
+ OSStatus deleteStatus = SecKeychainItemDelete(item);
+ CFRelease(item);
+ return deleteStatus;
+ }
+
+ return findStatus;
+}
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/QSThingsPlugin/Parsers/QSThingsAreasParser.m b/QSThingsPlugin/Parsers/QSThingsAreasParser.m
index d4f9b5f..f147603 100644
--- a/QSThingsPlugin/Parsers/QSThingsAreasParser.m
+++ b/QSThingsPlugin/Parsers/QSThingsAreasParser.m
@@ -21,9 +21,9 @@
QSObject *newObject = [QSObject
makeObjectWithIdentifier:[dictionary objectForKey:@"uuid"]];
- [newObject setObject:dictionary forType:kProjectType];
+ [newObject setObject:dictionary forType:kAreaType];
[newObject setName:[dictionary objectForKey:@"title"]];
- [newObject setPrimaryType:kProjectType];
+ [newObject setPrimaryType:kAreaType];
[objects addObject:newObject];
}];
diff --git a/QSThingsPlugin/QSThingsPluginSource.m b/QSThingsPlugin/QSThingsPluginSource.m
index 08984ef..f73b5ca 100644
--- a/QSThingsPlugin/QSThingsPluginSource.m
+++ b/QSThingsPlugin/QSThingsPluginSource.m
@@ -45,8 +45,7 @@
#pragma mark - Object Handler Methods
- (void)setQuickIconForObject:(QSObject *)object {
- [object
- setIcon:[QSResourceManager imageNamed:@"com.culturedcode.ThingsMac"]];
+ [object setIcon:[QSResourceManager imageNamed:@"com.culturedcode.ThingsMac"]];
}
- (BOOL)objectHasChildren:(QSObject *)object {
diff --git a/QSThingsPlugin/QSThingsPluginSource.xib b/QSThingsPlugin/QSThingsPluginSource.xib
deleted file mode 100644
index ac6254e..0000000
--- a/QSThingsPlugin/QSThingsPluginSource.xib
+++ /dev/null
@@ -1,50 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="24123.1" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
- <dependencies>
- <deployment identifier="macosx"/>
- <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="24123.1"/>
- <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
- </dependencies>
- <objects>
- <customObject id="-2" userLabel="File's Owner" customClass="QSThingsPluginSource">
- <connections>
- <outlet property="settingsView" destination="settings-view" id="settings-view-outlet"/>
- </connections>
- </customObject>
- <customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
- <customObject id="-3" userLabel="Application" customClass="NSObject"/>
- <customView translatesAutoresizingMaskIntoConstraints="NO" id="settings-view">
- <rect key="frame" x="0.0" y="0.0" width="249" height="252"/>
- <subviews>
- <textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="site-label">
- <rect key="frame" x="58" y="118" width="133" height="16"/>
- <constraints>
- <constraint firstAttribute="height" constant="16" id="Eoo-3h-Uin"/>
- </constraints>
- <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Nothing to configure!" id="site-label-cell">
- <font key="font" metaFont="system"/>
- <color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
- <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
- </textFieldCell>
- </textField>
- </subviews>
- <constraints>
- <constraint firstItem="site-label" firstAttribute="centerX" secondItem="settings-view" secondAttribute="centerX" id="D0Z-9a-PUk"/>
- <constraint firstItem="site-label" firstAttribute="centerY" secondItem="settings-view" secondAttribute="centerY" id="KkB-XD-j1l"/>
- </constraints>
- <point key="canvasLocation" x="155.5" y="180"/>
- </customView>
- <objectController id="JpK-62-U8F" userLabel="Settings">
- <declaredKeys>
- <string>info.username</string>
- <string>info.site</string>
- <string>info.includeTags</string>
- <string>info.host</string>
- </declaredKeys>
- <connections>
- <binding destination="-2" name="contentObject" keyPath="self.selectedEntry" id="r1i-y5-4Mi"/>
- <outlet property="content" destination="-2" id="UlK-eW-ayD"/>
- </connections>
- </objectController>
- </objects>
-</document>
diff --git a/QSThingsPlugin/Types/Constants.h b/QSThingsPlugin/Types/Constants.h
index e365a8f..044dcde 100644
--- a/QSThingsPlugin/Types/Constants.h
+++ b/QSThingsPlugin/Types/Constants.h
@@ -5,3 +5,7 @@ extern NSString *const kProjectType;
extern NSString *const kAreaType;
extern NSString *const kTagType;
extern NSString *const kTaskType;
+
+#pragma mark - Keychain
+extern NSString *const kKeychainService;
+extern NSString *const kAuthenticationKeyKeychainKey;
diff --git a/QSThingsPlugin/Types/Constants.m b/QSThingsPlugin/Types/Constants.m
index 4abf9f7..e6158a2 100644
--- a/QSThingsPlugin/Types/Constants.m
+++ b/QSThingsPlugin/Types/Constants.m
@@ -5,3 +5,7 @@ NSString *const kProjectType = @"things.project";
NSString *const kAreaType = @"things.area";
NSString *const kTagType = @"things.tag";
NSString *const kTaskType = @"things.task";
+
+#pragma mark - Keychain
+NSString *const kKeychainService = @"QSThingsPlugin";
+NSString *const kAuthenticationKeyKeychainKey = @"authenticationKey";