summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRuben Beltran del Rio <git@r.bdr.sh>2025-09-08 16:04:35 +0200
committerRuben Beltran del Rio <git@r.bdr.sh>2025-09-08 16:04:35 +0200
commitcec89d9e844dc39b84778e1b88e74e23f9ca2e6a (patch)
tree16e36fdc1263dff71510256a43dc0d972bf35c88
parenta5e25cc05e5e33e114331cc826211c89adbcfa6a (diff)
WIP generic Delicious API compatible
-rw-r--r--Factories/QSBookmarkProviderFactory.m10
-rw-r--r--Info.plist2
-rw-r--r--Protocols/QSBookmarkProvider.h15
-rw-r--r--Providers/QSDeliciousAPIProvider.h5
-rw-r--r--Providers/QSDeliciousAPIProvider.m93
-rw-r--r--Providers/QSLinkdingProvider.m25
-rw-r--r--QSDeliciousPlugIn.xcodeproj/project.pbxproj6
-rw-r--r--QSDeliciousPlugIn_Source.m59
-rw-r--r--QSDeliciousPlugIn_Source.xib64
-rw-r--r--Types/Constants.h9
-rw-r--r--Types/Constants.m8
-rw-r--r--Types/SocialSite.h7
-rw-r--r--Types/SocialSite.m22
13 files changed, 167 insertions, 158 deletions
diff --git a/Factories/QSBookmarkProviderFactory.m b/Factories/QSBookmarkProviderFactory.m
index 6fcfdfb..75fd0e2 100644
--- a/Factories/QSBookmarkProviderFactory.m
+++ b/Factories/QSBookmarkProviderFactory.m
@@ -34,17 +34,13 @@
- (void)setupProviders {
NSMutableArray *mutableProviders = [NSMutableArray array];
- // Create Pinboard API providers for each supported site
- QSDeliciousAPIProvider *deliciousProvider = [[QSDeliciousAPIProvider alloc] initWithSite:SocialSiteDelicious];
- QSDeliciousAPIProvider *magnoliaProvider = [[QSDeliciousAPIProvider alloc] initWithSite:SocialSiteMagnolia];
- QSDeliciousAPIProvider *pinboardProvider = [[QSDeliciousAPIProvider alloc] initWithSite:SocialSitePinboard];
+ // Create Delicious / Pinboard API providers for each supported site
+ QSDeliciousAPIProvider *deliciousAPIProvider = [[QSDeliciousAPIProvider alloc] init];
// Create Linkding provider
QSLinkdingProvider *linkdingProvider = [[QSLinkdingProvider alloc] init];
- [mutableProviders addObject:deliciousProvider];
- [mutableProviders addObject:magnoliaProvider];
- [mutableProviders addObject:pinboardProvider];
+ [mutableProviders addObject:deliciousAPIProvider];
[mutableProviders addObject:linkdingProvider];
self.providers = [mutableProviders copy];
diff --git a/Info.plist b/Info.plist
index b916b8c..5101e87 100644
--- a/Info.plist
+++ b/Info.plist
@@ -42,7 +42,7 @@
</dict>
<key>QSObjectHandlers</key>
<dict>
- <key>us.icio.del.tag</key>
+ <key>qs.tag.socialbookmark</key>
<string>QSDeliciousPlugIn_Source</string>
</dict>
<key>QSObjectSources</key>
diff --git a/Protocols/QSBookmarkProvider.h b/Protocols/QSBookmarkProvider.h
index 136ea2d..9131e3c 100644
--- a/Protocols/QSBookmarkProvider.h
+++ b/Protocols/QSBookmarkProvider.h
@@ -24,25 +24,10 @@
*/
- (NSArray *)fetchBookmarksForSite:(SocialSite)site username:(NSString *)username password:(NSString *)password identifier:(NSString *)identifier host:(NSString *)host includeTags:(BOOL)includeTags;
-/**
- * Get the supported site for this provider
- */
-- (SocialSite)supportedSite;
-
-/**
- * Get display name for this provider
- */
-- (NSString *)providerName;
-
@optional
/**
* Get bookmarks for a specific tag (used for child loading)
*/
- (NSArray *)fetchBookmarksForTag:(NSString *)tag site:(SocialSite)site username:(NSString *)username password:(NSString *)password host:(NSString *)host;
-/**
- * Get the tag URL type identifier for this provider
- */
-- (NSString *)tagURLType;
-
@end
diff --git a/Providers/QSDeliciousAPIProvider.h b/Providers/QSDeliciousAPIProvider.h
index 0064264..08a0534 100644
--- a/Providers/QSDeliciousAPIProvider.h
+++ b/Providers/QSDeliciousAPIProvider.h
@@ -12,12 +12,9 @@
@interface QSDeliciousAPIProvider : NSObject <QSBookmarkProvider, NSXMLParserDelegate>
@property (nonatomic, strong) NSMutableArray *posts;
-@property (nonatomic, assign) SocialSite site;
-
-- (instancetype)initWithSite:(SocialSite)site;
// Subclasses can override these methods
-- (NSString *)apiURLForSite:(SocialSite)site;
+- (NSString *)apiURLForSite:(SocialSite)site andHost:(NSString *)host;
- (NSURL *)requestURLForSite:(SocialSite)site username:(NSString *)username password:(NSString *)password host:(NSString *)host;
- (NSData *)cachedBookmarkDataForSite:(SocialSite)site username:(NSString *)username;
- (void)cacheBookmarkData:(NSData *)data forSite:(SocialSite)site username:(NSString *)username;
diff --git a/Providers/QSDeliciousAPIProvider.m b/Providers/QSDeliciousAPIProvider.m
index ab09f88..31b283d 100644
--- a/Providers/QSDeliciousAPIProvider.m
+++ b/Providers/QSDeliciousAPIProvider.m
@@ -5,52 +5,59 @@
#import "QSDeliciousAPIProvider.h"
#import "SocialSite.h"
+#import "Constants.h"
#import <QSCore/QSCore.h>
@implementation QSDeliciousAPIProvider
-- (instancetype)initWithSite:(SocialSite)site {
- self = [super init];
- if (self) {
- _site = site;
- _posts = [NSMutableArray array];
- }
- return self;
-}
-
- (BOOL)canHandleSite:(SocialSite)site username:(NSString *)username password:(NSString *)password host:(NSString *)host {
- return (site == self.site) &&
- username.length > 0 &&
+ return (
+ site == SocialSiteDelicious ||
+ site == SocialSiteMagnolia ||
+ site == SocialSitePinboard ||
+ site == SocialSiteSelfHostedDeliciousCompatible
+ ) &&
+ username.length > 0 &&
password.length > 0 &&
- (site != SocialSiteLinkding); // This provider doesn't handle Linkding
-}
-
-- (SocialSite)supportedSite {
- return self.site;
+ (site != SocialSiteSelfHostedDeliciousCompatible || host.length > 0);
}
-- (NSString *)providerName {
- return [SocialSiteHelper displayNameForSite:self.site];
-}
-
-- (NSString *)apiURLForSite:(SocialSite)site {
+- (NSString *)apiURLForSite:(SocialSite)site andHost:(NSString *)host {
switch (site) {
case SocialSiteDelicious:
return @"api.del.icio.us/v1";
case SocialSiteMagnolia:
return @"ma.gnolia.com/api/mirrord/v1";
case SocialSitePinboard:
- return @"api.pinboard.in/v1";
+ return @"https://api.pinboard.in/v1";
+ case SocialSiteSelfHostedDeliciousCompatible:
+ return [NSString stringWithFormat:@"%@/v1", host];
default:
return nil;
}
}
+- (BOOL)usesAuthToken:(SocialSite)site {
+ switch (site) {
+ case SocialSitePinboard:
+ case SocialSiteSelfHostedDeliciousCompatible:
+ return YES;
+ default:
+ return NO;
+ }
+}
- (NSURL *)requestURLForSite:(SocialSite)site username:(NSString *)username password:(NSString *)password host:(NSString *)host {
- NSString *apiURL = [self apiURLForSite:site];
+ NSString *apiURL = [self apiURLForSite:site andHost: host];
if (!apiURL) return nil;
-
- NSString *urlString = [NSString stringWithFormat:@"https://%@:%@@%@/posts/all?", username, password, apiURL];
+
+ NSString *urlString;
+ if ([self usesAuthToken:site]) {
+ // Pinboard and pinboard compatible sites require an
+ // auth token rather than a password.
+ urlString = [NSString stringWithFormat:@"%@/posts/all?auth_token=%@:%@", apiURL, username, password];
+ } else {
+ urlString = [NSString stringWithFormat:@"https://%@:%@@%@/posts/all?", username, password, apiURL];
+ }
return [NSURL URLWithString:urlString];
}
@@ -61,15 +68,12 @@
}
- (void)cacheBookmarkData:(NSData *)data forSite:(SocialSite)site username:(NSString *)username {
- NSString *siteURL = [SocialSiteHelper siteURLForSite:site];
- NSString *cachePath = [QSApplicationSupportSubPath([NSString stringWithFormat:@"Caches/%@/", siteURL], YES) stringByAppendingPathComponent:[NSString stringWithFormat:@"%@.xml", username]];
+ NSString *siteURL = [SocialSiteHelper siteURLForSite:site];
+ NSString *safeURL = [[siteURL componentsSeparatedByCharactersInSet:[[NSCharacterSet alphanumericCharacterSet] invertedSet]] componentsJoinedByString:@"-"];
+ NSString *cachePath = [QSApplicationSupportSubPath([NSString stringWithFormat:@"Caches/%@/", safeURL], YES) stringByAppendingPathComponent:[NSString stringWithFormat:@"%@.xml", username]];
[data writeToFile:cachePath atomically:NO];
}
-- (NSString *)tagURLType {
- return [NSString stringWithFormat:@"tag.%@", [SocialSiteHelper reversedSiteURLForSite:self.site]];
-}
-
- (NSArray *)fetchBookmarksForSite:(SocialSite)site username:(NSString *)username password:(NSString *)password identifier:(NSString *)identifier host:(NSString *)host includeTags:(BOOL)includeTags {
// Try cached data first
@@ -79,7 +83,7 @@
if (![data length]) {
NSURL *requestURL = [self requestURLForSite:site username:username password:password host:host];
if (!requestURL) return @[];
-
+
NSMutableURLRequest *theRequest = [NSMutableURLRequest requestWithURL:requestURL
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:60.0];
@@ -87,7 +91,7 @@
[theRequest setValue:@"text/xml" forHTTPHeaderField:@"Content-type"];
[theRequest setValue:@"Quicksilver (Blacktree,MacOSX)" forHTTPHeaderField:@"User-Agent"];
- NSError *error;
+ NSError *error = nil;
data = [NSURLConnection sendSynchronousRequest:theRequest returningResponse:nil error:&error];
if (error) {
@@ -98,6 +102,8 @@
// Cache the data
[self cacheBookmarkData:data forSite:site username:username];
}
+
+ NSLog(@"WE ARE READY TO PARSE");
// Parse XML data
NSXMLParser *postParser = [[NSXMLParser alloc] initWithData:data];
@@ -105,6 +111,8 @@
self.posts = [NSMutableArray arrayWithCapacity:1];
[postParser parse];
+
+ NSLog(@"STILL READY: %ld", [self.posts count]);
NSMutableArray *objects = [NSMutableArray arrayWithCapacity:1];
NSMutableSet *tagSet = [NSMutableSet set];
@@ -129,15 +137,16 @@
if (includeTags) {
for (NSString *tag in tagSet) {
if (tag.length > 0) {
- QSObject *tagObject = [QSObject makeObjectWithIdentifier:[NSString stringWithFormat:@"[%@ tag]:%@", [self providerName], tag]];
- [tagObject setObject:tag forType:[self tagURLType]];
- [tagObject setObject:@(site) forMeta:@"source.site"];
- [tagObject setObject:username forMeta:@"source.username"];
- [tagObject setObject:host forMeta:@"source.host"];
- [tagObject setObject:identifier forMeta:@"source.identifier"];
- [tagObject setName:tag];
- [tagObject setPrimaryType:[self tagURLType]];
- [objects addObject:tagObject];
+ QSObject *tagObject = [QSObject makeObjectWithIdentifier:[NSString stringWithFormat:@"[%@ tag]:%@", [SocialSiteHelper displayNameForSite:site], tag]];
+
+ [tagObject setObject:tag forType:kTagType];
+ [tagObject setObject:@(site) forMeta:kTagSiteField];
+ [tagObject setObject:username forMeta:kTagUsernameField];
+ [tagObject setObject:host forMeta:kTagHostField];
+ [tagObject setObject:identifier forMeta:kTagIdentifierField];
+ [tagObject setName:tag];
+ [tagObject setPrimaryType:kTagType];
+ [objects addObject:tagObject];
}
}
}
diff --git a/Providers/QSLinkdingProvider.m b/Providers/QSLinkdingProvider.m
index 094f318..c7bf33f 100644
--- a/Providers/QSLinkdingProvider.m
+++ b/Providers/QSLinkdingProvider.m
@@ -5,6 +5,7 @@
#import "QSLinkdingProvider.h"
#import "SocialSite.h"
+#import "Constants.h"
#import <QSCore/QSCore.h>
@implementation QSLinkdingProvider
@@ -16,18 +17,6 @@
host.length > 0;
}
-- (SocialSite)supportedSite {
- return SocialSiteLinkding;
-}
-
-- (NSString *)providerName {
- return @"Linkding";
-}
-
-- (NSString *)tagURLType {
- return @"tag.linkding";
-}
-
- (NSData *)cachedBookmarkDataForHost:(NSString *)host username:(NSString *)username {
// Create a safe filename from host
NSString *safeHost = [[host componentsSeparatedByCharactersInSet:[[NSCharacterSet alphanumericCharacterSet] invertedSet]] componentsJoinedByString:@"-"];
@@ -129,14 +118,14 @@
for (NSString *tag in tagSet) {
if (tag.length > 0) {
QSObject *tagObject = [QSObject makeObjectWithIdentifier:[NSString stringWithFormat:@"[Linkding tag]:%@", tag]];
- [tagObject setObject:tag forType:[self tagURLType]];
- [tagObject setObject:@(site) forMeta:@"source.site"];
- [tagObject setObject:username forMeta:@"source.username"];
- [tagObject setObject:host forMeta:@"source.host"];
+ [tagObject setObject:tag forType:kTagType];
+ [tagObject setObject:@(site) forMeta:kTagSiteField];
+ [tagObject setObject:username forMeta:kTagUsernameField];
+ [tagObject setObject:host forMeta:kTagHostField];
// We need the identifier to be able to fetch the keychain password
- [tagObject setObject:identifier forMeta:@"source.identifier"];
+ [tagObject setObject:identifier forMeta:kTagIdentifierField];
[tagObject setName:tag];
- [tagObject setPrimaryType:[self tagURLType]];
+ [tagObject setPrimaryType:kTagType];
[objects addObject:tagObject];
}
}
diff --git a/QSDeliciousPlugIn.xcodeproj/project.pbxproj b/QSDeliciousPlugIn.xcodeproj/project.pbxproj
index d68169d..803ae6e 100644
--- a/QSDeliciousPlugIn.xcodeproj/project.pbxproj
+++ b/QSDeliciousPlugIn.xcodeproj/project.pbxproj
@@ -13,6 +13,7 @@
7FB0DC2F0B91FFC600A5B6FF /* QSCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7FB0DC2D0B91FFC500A5B6FF /* QSCore.framework */; };
7FB0DC300B91FFC600A5B6FF /* QSFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7FB0DC2E0B91FFC600A5B6FF /* QSFoundation.framework */; };
8D1AC9700486D14A00FE50C9 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DD92D38A0106425D02CA0E72 /* Cocoa.framework */; };
+ B59368092E6F04E200DBD0F1 /* Constants.m in Sources */ = {isa = PBXBuildFile; fileRef = B59368082E6F04DF00DBD0F1 /* Constants.m */; };
B5CF7D6A2E6B7631008A0EE6 /* QSDeliciousPlugIn_Source.xib in Resources */ = {isa = PBXBuildFile; fileRef = B5CF7D692E6B74FE008A0EE6 /* QSDeliciousPlugIn_Source.xib */; };
B5CF7D6E2E6B7A53008A0EE6 /* QSDeliciousAPIProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = B5CF7D602E6B73A0008A0EE6 /* QSDeliciousAPIProvider.m */; };
B5CF7D6F2E6B7A53008A0EE6 /* QSBookmarkProviderFactory.m in Sources */ = {isa = PBXBuildFile; fileRef = B5CF7D642E6B73DA008A0EE6 /* QSBookmarkProviderFactory.m */; };
@@ -33,6 +34,8 @@
7FB0DC2E0B91FFC600A5B6FF /* QSFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QSFoundation.framework; path = /Applications/Quicksilver.app/Contents/Frameworks/QSFoundation.framework; sourceTree = "<absolute>"; };
8D1AC9730486D14A00FE50C9 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
8D1AC9740486D14A00FE50C9 /* Social Bookmarks Plugin.qsplugin */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Social Bookmarks Plugin.qsplugin"; sourceTree = BUILT_PRODUCTS_DIR; };
+ B59368072E6F021E00DBD0F1 /* Constants.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Constants.h; sourceTree = "<group>"; };
+ B59368082E6F04DF00DBD0F1 /* Constants.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = Constants.m; sourceTree = "<group>"; };
B5CF7D5C2E6B7370008A0EE6 /* SocialSite.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SocialSite.h; sourceTree = "<group>"; };
B5CF7D5D2E6B7377008A0EE6 /* SocialSite.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SocialSite.m; sourceTree = "<group>"; };
B5CF7D5E2E6B737E008A0EE6 /* QSBookmarkProvider.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = QSBookmarkProvider.h; sourceTree = "<group>"; };
@@ -153,6 +156,8 @@
B59368042E6EC65000DBD0F1 /* Types */ = {
isa = PBXGroup;
children = (
+ B59368072E6F021E00DBD0F1 /* Constants.h */,
+ B59368082E6F04DF00DBD0F1 /* Constants.m */,
B5CF7D5C2E6B7370008A0EE6 /* SocialSite.h */,
B5CF7D5D2E6B7377008A0EE6 /* SocialSite.m */,
);
@@ -288,6 +293,7 @@
B5CF7D702E6B7A53008A0EE6 /* QSLinkdingProvider.m in Sources */,
B5CF7D712E6B7A53008A0EE6 /* SocialSite.m in Sources */,
E182BCD406FC8203007BF2C2 /* QSDeliciousPlugIn_Source.m in Sources */,
+ B59368092E6F04E200DBD0F1 /* Constants.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
diff --git a/QSDeliciousPlugIn_Source.m b/QSDeliciousPlugIn_Source.m
index 55eedf0..4d841a1 100644
--- a/QSDeliciousPlugIn_Source.m
+++ b/QSDeliciousPlugIn_Source.m
@@ -7,9 +7,9 @@
//
#import "QSDeliciousPlugIn_Source.h"
+#import "Constants.h"
#import <QSCore/QSCore.h>
#import <Security/Security.h>
-#import <os/log.h>
@implementation QSDeliciousPlugIn_Source
@@ -72,6 +72,8 @@
// to force the catalog to save the current values.
- (IBAction)settingsChanged:(id)sender {
[[NSNotificationCenter defaultCenter] postNotificationName:QSCatalogEntryChangedNotification object:self.selectedEntry];
+ [self willChangeValueForKey:@"isHostVisible"];
+ [self didChangeValueForKey:@"isHostVisible"];
}
@@ -198,6 +200,12 @@
});
}
+#pragma mark - Host Visibility Control
+- (BOOL) isHostVisible {
+ return [SocialSiteHelper hasVariableHost:[self siteIndex]];
+}
+- (void)setIsHostVisible:(BOOL)isVisible { }
+
#pragma mark - Objects For Entry
- (NSArray *)objectsForEntry:(QSCatalogEntry *)theEntry {
@@ -219,23 +227,22 @@
NSLog(@"No provider available for site %ld with username %@", (long)site, username);
return @[];
}
-
+
return [provider fetchBookmarksForSite:site username:username password:password identifier:identifier host:host includeTags:includeTags];
}
-- (NSArray *)objectsForTag:(NSString *)tag username:(NSString *)username {
- SocialSite site = [self siteIndex];
- NSString *password = [self currentPassword];
- NSString *host = [self currentHost];
-
- // Get the appropriate provider using the factory
- QSBookmarkProviderFactory *factory = [QSBookmarkProviderFactory sharedFactory];
- id<QSBookmarkProvider> provider = [factory providerForSite:site username:username password:password host:host];
+- (NSArray *)objectsForTag:(NSString *)tag site:(SocialSite)site username:(NSString *)username identifier:(NSString *)identifier host:(NSString *)host {
+
+ NSString *keychainKey = [self keychainKeyForIdentifier:identifier];
+ NSString *password = [self passwordFromKeychainForKey:keychainKey];
+
+ QSBookmarkProviderFactory *factory = [QSBookmarkProviderFactory sharedFactory];
+ id<QSBookmarkProvider> provider = [factory providerForSite:site username:username password:password host:host];
- if (!provider) {
- NSLog(@"No provider available for site %ld with username %@", (long)site, username);
- return @[];
- }
+ if (!provider) {
+ NSLog(@"No provider available for site %ld with username %@", (long)site, username);
+ return @[];
+ }
// Check if provider supports tag-based fetching
if ([provider respondsToSelector:@selector(fetchBookmarksForTag:site:username:password:host:)]) {
@@ -248,9 +255,17 @@
#pragma mark - Object Handler Methods
- (void)setQuickIconForObject:(QSObject *)object {
- [object setIcon:[[NSBundle bundleForClass:[self class]]imageNamed:@"bookmark_icon"]];
+ if (@available(macOS 11.0, *)) {
+ NSImage *image = [NSImage imageWithSystemSymbolName:@"tag" accessibilityDescription:@"Bookmark"];
+ [object setIcon:image];
+ } else {
+ [object setIcon:[[NSBundle bundleForClass:[self class]]imageNamed:@"bookmark_icon"]];
+ }
}
+// All our objects will have children. URLs will have tags, and tags will have URLs.
+- (BOOL)objectHasChildren:(QSObject *) object { return YES; }
+
// This will receive a tag object. Tag objects will have the
// source configuration in the meta: source.username,
// source.site, source.host and source.identifier.
@@ -281,21 +296,13 @@
return NO;
}
- NSString *tagType = nil;
- if (site == SocialSiteLinkding) {
- tagType = @"tag.linkding";
- } else {
- NSString *reversedURL = [SocialSiteHelper reversedSiteURLForSite:site];
- tagType = [NSString stringWithFormat:@"tag.%@", reversedURL];
- }
-
- NSString *tag = [object objectForType:tagType];
+ NSString *tag = [object objectForType:kTagType];
if (!tag) {
NSLog(@"We could not find a valid tag type.");
return NO;
}
-
- NSArray *children = [self objectsForTag:tag username:username];
+
+ NSArray *children = [self objectsForTag:tag site:site username:username identifier:identifier host:host];
[object setChildren:children];
return YES;
}
diff --git a/QSDeliciousPlugIn_Source.xib b/QSDeliciousPlugIn_Source.xib
index c688346..5f56ddc 100644
--- a/QSDeliciousPlugIn_Source.xib
+++ b/QSDeliciousPlugIn_Source.xib
@@ -33,7 +33,7 @@
</textFieldCell>
</textField>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" id="username-label">
- <rect key="frame" x="18" y="179" width="70" height="16"/>
+ <rect key="frame" x="18" y="147" width="70" height="16"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<constraints>
<constraint firstAttribute="height" constant="16" id="y8T-Rp-nV6"/>
@@ -45,7 +45,7 @@
</textFieldCell>
</textField>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" id="password-label">
- <rect key="frame" x="22" y="140" width="66" height="16"/>
+ <rect key="frame" x="22" y="108" width="66" height="16"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<constraints>
<constraint firstAttribute="height" constant="16" id="BpN-ax-hN6"/>
@@ -57,7 +57,7 @@
</textFieldCell>
</textField>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" id="host-label">
- <rect key="frame" x="51" y="101" width="37" height="16"/>
+ <rect key="frame" x="51" y="69" width="37" height="16"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<constraints>
<constraint firstAttribute="height" constant="16" id="SmD-SH-xQe"/>
@@ -67,31 +67,14 @@
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
- </textField>
- <textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" id="host-help-label">
- <rect key="frame" x="94" y="75" width="137" height="14"/>
- <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
- <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="left" title="Required for Linkding" id="host-help-label-cell">
- <font key="font" metaFont="smallSystem"/>
- <color key="textColor" name="secondaryLabelColor" catalog="System" colorSpace="catalog"/>
- <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
- </textFieldCell>
- </textField>
- <button id="include-tags-checkbox">
- <rect key="frame" x="96" y="52" width="99" height="16"/>
- <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
- <buttonCell key="cell" type="check" title="Include Tags" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="include-tags-checkbox-cell">
- <behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
- <font key="font" metaFont="system"/>
- </buttonCell>
- <constraints>
- <constraint firstAttribute="height" constant="16" id="egr-kg-1on"/>
- </constraints>
<connections>
- <action selector="settingsChanged:" target="-2" id="BfD-Jh-b7j"/>
- <binding destination="-2" name="value" keyPath="selectedEntry.sourceSettings.includeTags" id="lGp-jw-qTE"/>
+ <binding destination="-2" name="hidden" keyPath="isHostVisible" id="IaX-Z5-bel">
+ <dictionary key="options">
+ <string key="NSValueTransformerName">NSNegateBoolean</string>
+ </dictionary>
+ </binding>
</connections>
- </button>
+ </textField>
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="site-popup">
<rect key="frame" x="96" y="214" width="133" height="24"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
@@ -103,7 +86,8 @@
<menuItem title="del.icio.us" state="on" id="site-delicious"/>
<menuItem title="ma.gnolia.com" tag="1" id="site-magnolia"/>
<menuItem title="Pinboard" tag="2" id="site-pinboard"/>
- <menuItem title="Linkding" tag="3" id="site-linkding"/>
+ <menuItem title="Linkding" tag="3" id="NKh-uv-BH6"/>
+ <menuItem title="Self-Hosted (Delicious API)" tag="4" id="site-linkding"/>
</items>
</menu>
</popUpButtonCell>
@@ -113,7 +97,7 @@
</connections>
</popUpButton>
<textField focusRingType="none" verticalHuggingPriority="750" id="username-text-field">
- <rect key="frame" x="96" y="175" width="133" height="24"/>
+ <rect key="frame" x="96" y="143" width="133" height="24"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<constraints>
<constraint firstAttribute="height" constant="24" id="fas-E9-Pf6"/>
@@ -129,7 +113,7 @@
</connections>
</textField>
<secureTextField focusRingType="none" verticalHuggingPriority="750" id="password-text-field">
- <rect key="frame" x="96" y="136" width="133" height="24"/>
+ <rect key="frame" x="96" y="104" width="133" height="24"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<constraints>
<constraint firstAttribute="height" constant="24" id="dQF-Rc-KB8"/>
@@ -148,7 +132,7 @@
</connections>
</secureTextField>
<textField focusRingType="none" verticalHuggingPriority="750" id="host-text-field">
- <rect key="frame" x="96" y="97" width="133" height="24"/>
+ <rect key="frame" x="96" y="65" width="133" height="24"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<constraints>
<constraint firstAttribute="height" constant="24" id="bfW-V3-8A5"/>
@@ -160,9 +144,29 @@
</textFieldCell>
<connections>
<action selector="settingsChanged:" target="-2" id="4GR-6l-ZnG"/>
+ <binding destination="-2" name="hidden" keyPath="isHostVisible" id="0Uj-hV-LYQ">
+ <dictionary key="options">
+ <string key="NSValueTransformerName">NSNegateBoolean</string>
+ </dictionary>
+ </binding>
<binding destination="-2" name="value" keyPath="selectedEntry.sourceSettings.host" id="7qk-HC-e8v"/>
</connections>
</textField>
+ <button id="include-tags-checkbox">
+ <rect key="frame" x="96" y="183" width="99" height="16"/>
+ <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
+ <buttonCell key="cell" type="check" title="Include Tags" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="include-tags-checkbox-cell">
+ <behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
+ <font key="font" metaFont="system"/>
+ </buttonCell>
+ <constraints>
+ <constraint firstAttribute="height" constant="16" id="egr-kg-1on"/>
+ </constraints>
+ <connections>
+ <action selector="settingsChanged:" target="-2" id="BfD-Jh-b7j"/>
+ <binding destination="-2" name="value" keyPath="selectedEntry.sourceSettings.includeTags" id="lGp-jw-qTE"/>
+ </connections>
+ </button>
</subviews>
<point key="canvasLocation" x="155.5" y="180"/>
</customView>
diff --git a/Types/Constants.h b/Types/Constants.h
new file mode 100644
index 0000000..75dd240
--- /dev/null
+++ b/Types/Constants.h
@@ -0,0 +1,9 @@
+
+#pragma mark - Tag QSObject Constants
+
+extern NSString *const kTagType;
+
+extern NSString *const kTagSiteField;
+extern NSString *const kTagUsernameField;
+extern NSString *const kTagHostField;
+extern NSString *const kTagIdentifierField;
diff --git a/Types/Constants.m b/Types/Constants.m
new file mode 100644
index 0000000..9300b54
--- /dev/null
+++ b/Types/Constants.m
@@ -0,0 +1,8 @@
+#pragma mark - Tag QSObject Constants
+
+NSString *const kTagType = @"qs.tag.socialbookmark";
+
+NSString *const kTagSiteField = @"source.site";
+NSString *const kTagUsernameField = @"source.username";
+NSString *const kTagHostField = @"source.host";
+NSString *const kTagIdentifierField = @"source.identifier";
diff --git a/Types/SocialSite.h b/Types/SocialSite.h
index f2c848c..130bc40 100644
--- a/Types/SocialSite.h
+++ b/Types/SocialSite.h
@@ -11,13 +11,14 @@ typedef NS_ENUM(NSInteger, SocialSite) {
SocialSiteDelicious = 0,
SocialSiteMagnolia = 1,
SocialSitePinboard = 2,
- SocialSiteLinkding = 3
+ SocialSiteLinkding = 3,
+ SocialSiteSelfHostedDeliciousCompatible = 4
};
@interface SocialSiteHelper : NSObject
+ (NSString *)displayNameForSite:(SocialSite)site;
+ (NSString *)siteURLForSite:(SocialSite)site;
-+ (NSString *)reversedSiteURLForSite:(SocialSite)site;
++ (BOOL)hasVariableHost:(SocialSite)site;
-@end \ No newline at end of file
+@end
diff --git a/Types/SocialSite.m b/Types/SocialSite.m
index d002235..b73533f 100644
--- a/Types/SocialSite.m
+++ b/Types/SocialSite.m
@@ -17,6 +17,8 @@
return @"Pinboard";
case SocialSiteLinkding:
return @"Linkding";
+ case SocialSiteSelfHostedDeliciousCompatible:
+ return @"Self-Hosted (Delicious Compatible)";
default:
return @"Unknown";
}
@@ -30,6 +32,7 @@
return @"ma.gnolia.com";
case SocialSitePinboard:
return @"pinboard.in";
+ case SocialSiteSelfHostedDeliciousCompatible:
case SocialSiteLinkding:
return @""; // Will be provided by user as host
default:
@@ -37,19 +40,14 @@
}
}
-+ (NSString *)reversedSiteURLForSite:(SocialSite)site {
++ (BOOL)hasVariableHost:(SocialSite)site {
switch (site) {
- case SocialSiteDelicious:
- return @"us.icio.del";
- case SocialSiteMagnolia:
- return @"com.gnolia.ma";
- case SocialSitePinboard:
- return @"in.pinboard";
- case SocialSiteLinkding:
- return @"linkding"; // Generic identifier
- default:
- return nil;
+ case SocialSiteSelfHostedDeliciousCompatible:
+ case SocialSiteLinkding:
+ return YES;
+ default:
+ return NO;
}
}
-@end \ No newline at end of file
+@end