First stab at a new 'Subscribe to Feed' Safari App Extension.
This commit is contained in:
parent
06b4c451a6
commit
3cb6ee35ab
|
@ -7,6 +7,12 @@
|
|||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
6581C73820CED60100F4AD34 /* SafariExtensionHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6581C73720CED60100F4AD34 /* SafariExtensionHandler.swift */; };
|
||||
6581C73A20CED60100F4AD34 /* SafariExtensionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6581C73920CED60100F4AD34 /* SafariExtensionViewController.swift */; };
|
||||
6581C73D20CED60100F4AD34 /* SafariExtensionViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6581C73B20CED60100F4AD34 /* SafariExtensionViewController.xib */; };
|
||||
6581C74020CED60100F4AD34 /* evergreen-subscribe-to-feed.js in Resources */ = {isa = PBXBuildFile; fileRef = 6581C73F20CED60100F4AD34 /* evergreen-subscribe-to-feed.js */; };
|
||||
6581C74220CED60100F4AD34 /* ToolbarItemIcon.pdf in Resources */ = {isa = PBXBuildFile; fileRef = 6581C74120CED60100F4AD34 /* ToolbarItemIcon.pdf */; };
|
||||
6581C74620CED60100F4AD34 /* Subscribe to Feed.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 6581C73320CED60000F4AD34 /* Subscribe to Feed.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||
840D617F2029031C009BC708 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 840D617E2029031C009BC708 /* AppDelegate.swift */; };
|
||||
840D61812029031C009BC708 /* MasterViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 840D61802029031C009BC708 /* MasterViewController.swift */; };
|
||||
840D61832029031C009BC708 /* DetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 840D61822029031C009BC708 /* DetailViewController.swift */; };
|
||||
|
@ -203,6 +209,13 @@
|
|||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
6581C74420CED60100F4AD34 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 849C64581ED37A5D003D8FC0 /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 6581C73220CED60000F4AD34;
|
||||
remoteInfo = "Subscribe to Feed";
|
||||
};
|
||||
840D61922029031D009BC708 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 849C64581ED37A5D003D8FC0 /* Project object */;
|
||||
|
@ -458,6 +471,17 @@
|
|||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
6581C75720CED60100F4AD34 /* Embed App Extensions */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = "";
|
||||
dstSubfolderSpec = 13;
|
||||
files = (
|
||||
6581C74620CED60100F4AD34 /* Subscribe to Feed.appex in Embed App Extensions */,
|
||||
);
|
||||
name = "Embed App Extensions";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
84B06F681ED37B9000F0B54B /* Embed Frameworks */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
|
@ -506,6 +530,15 @@
|
|||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
6581C73320CED60000F4AD34 /* Subscribe to Feed.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "Subscribe to Feed.appex"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
6581C73420CED60100F4AD34 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; };
|
||||
6581C73720CED60100F4AD34 /* SafariExtensionHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SafariExtensionHandler.swift; sourceTree = "<group>"; };
|
||||
6581C73920CED60100F4AD34 /* SafariExtensionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SafariExtensionViewController.swift; sourceTree = "<group>"; };
|
||||
6581C73C20CED60100F4AD34 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/SafariExtensionViewController.xib; sourceTree = "<group>"; };
|
||||
6581C73E20CED60100F4AD34 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
6581C73F20CED60100F4AD34 /* evergreen-subscribe-to-feed.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; path = "evergreen-subscribe-to-feed.js"; sourceTree = "<group>"; };
|
||||
6581C74120CED60100F4AD34 /* ToolbarItemIcon.pdf */ = {isa = PBXFileReference; lastKnownFileType = image.pdf; path = ToolbarItemIcon.pdf; sourceTree = "<group>"; };
|
||||
6581C74320CED60100F4AD34 /* Subscribe_to_Feed.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Subscribe_to_Feed.entitlements; sourceTree = "<group>"; };
|
||||
8403E75A201C4A79007F7246 /* FeedListKeyboardDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedListKeyboardDelegate.swift; sourceTree = "<group>"; };
|
||||
840D617C2029031C009BC708 /* Evergreen.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Evergreen.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
840D617E2029031C009BC708 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||
|
@ -700,6 +733,14 @@
|
|||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
6581C73020CED60000F4AD34 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
6581C73520CED60100F4AD34 /* Cocoa.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
840D61792029031C009BC708 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
|
@ -749,6 +790,20 @@
|
|||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
6581C73620CED60100F4AD34 /* Safari Extension */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
6581C73720CED60100F4AD34 /* SafariExtensionHandler.swift */,
|
||||
6581C73920CED60100F4AD34 /* SafariExtensionViewController.swift */,
|
||||
6581C73B20CED60100F4AD34 /* SafariExtensionViewController.xib */,
|
||||
6581C73E20CED60100F4AD34 /* Info.plist */,
|
||||
6581C73F20CED60100F4AD34 /* evergreen-subscribe-to-feed.js */,
|
||||
6581C74120CED60100F4AD34 /* ToolbarItemIcon.pdf */,
|
||||
6581C74320CED60100F4AD34 /* Subscribe_to_Feed.entitlements */,
|
||||
);
|
||||
path = "Safari Extension";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
840D617D2029031C009BC708 /* Evergreen-iOS */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -1120,6 +1175,7 @@
|
|||
840D617D2029031C009BC708 /* Evergreen-iOS */,
|
||||
840D61942029031D009BC708 /* Evergreen-iOSTests */,
|
||||
840D619F2029031E009BC708 /* Evergreen-iOSUITests */,
|
||||
6581C73620CED60100F4AD34 /* Safari Extension */,
|
||||
84FB9A2C1EDCD6A4003D53B9 /* Frameworks */,
|
||||
849C64741ED37A5D003D8FC0 /* EvergreenTests */,
|
||||
D5907CDA2002F084005947E5 /* xcconfig */,
|
||||
|
@ -1145,6 +1201,7 @@
|
|||
840D617C2029031C009BC708 /* Evergreen.app */,
|
||||
840D61912029031D009BC708 /* Evergreen-iOSTests.xctest */,
|
||||
840D619C2029031D009BC708 /* Evergreen-iOSUITests.xctest */,
|
||||
6581C73320CED60000F4AD34 /* Subscribe to Feed.appex */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
|
@ -1307,6 +1364,7 @@
|
|||
children = (
|
||||
847752FE2008879500D93690 /* CoreServices.framework */,
|
||||
84FB9A2D1EDCD6B8003D53B9 /* Sparkle.framework */,
|
||||
6581C73420CED60100F4AD34 /* Cocoa.framework */,
|
||||
);
|
||||
name = Frameworks;
|
||||
path = Evergreen/Extensions;
|
||||
|
@ -1387,6 +1445,23 @@
|
|||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
6581C73220CED60000F4AD34 /* Subscribe to Feed */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 6581C75620CED60100F4AD34 /* Build configuration list for PBXNativeTarget "Subscribe to Feed" */;
|
||||
buildPhases = (
|
||||
6581C72F20CED60000F4AD34 /* Sources */,
|
||||
6581C73020CED60000F4AD34 /* Frameworks */,
|
||||
6581C73120CED60000F4AD34 /* Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = "Subscribe to Feed";
|
||||
productName = "Subscribe to Feed";
|
||||
productReference = 6581C73320CED60000F4AD34 /* Subscribe to Feed.appex */;
|
||||
productType = "com.apple.product-type.app-extension";
|
||||
};
|
||||
840D617B2029031C009BC708 /* Evergreen-iOS */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 840D61A32029031E009BC708 /* Build configuration list for PBXNativeTarget "Evergreen-iOS" */;
|
||||
|
@ -1449,6 +1524,7 @@
|
|||
849C645E1ED37A5D003D8FC0 /* Resources */,
|
||||
84C987A52000AC9E0066B150 /* ShellScript */,
|
||||
84B06F681ED37B9000F0B54B /* Embed Frameworks */,
|
||||
6581C75720CED60100F4AD34 /* Embed App Extensions */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
|
@ -1463,6 +1539,7 @@
|
|||
84BB4B7A1F11753300858766 /* PBXTargetDependency */,
|
||||
846E77401F6EF67A00A165E2 /* PBXTargetDependency */,
|
||||
846E77441F6EF6A100A165E2 /* PBXTargetDependency */,
|
||||
6581C74520CED60100F4AD34 /* PBXTargetDependency */,
|
||||
);
|
||||
name = Evergreen;
|
||||
productName = Evergreen;
|
||||
|
@ -1588,6 +1665,7 @@
|
|||
840D617B2029031C009BC708 /* Evergreen-iOS */,
|
||||
840D61902029031D009BC708 /* Evergreen-iOSTests */,
|
||||
840D619B2029031D009BC708 /* Evergreen-iOSUITests */,
|
||||
6581C73220CED60000F4AD34 /* Subscribe to Feed */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
@ -1757,6 +1835,16 @@
|
|||
/* End PBXReferenceProxy section */
|
||||
|
||||
/* Begin PBXResourcesBuildPhase section */
|
||||
6581C73120CED60000F4AD34 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
6581C74220CED60100F4AD34 /* ToolbarItemIcon.pdf in Resources */,
|
||||
6581C73D20CED60100F4AD34 /* SafariExtensionViewController.xib in Resources */,
|
||||
6581C74020CED60100F4AD34 /* evergreen-subscribe-to-feed.js in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
840D617A2029031C009BC708 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
|
@ -1834,6 +1922,15 @@
|
|||
/* End PBXShellScriptBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
6581C72F20CED60000F4AD34 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
6581C73A20CED60100F4AD34 /* SafariExtensionViewController.swift in Sources */,
|
||||
6581C73820CED60100F4AD34 /* SafariExtensionHandler.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
840D61782029031C009BC708 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
|
@ -2005,6 +2102,11 @@
|
|||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXTargetDependency section */
|
||||
6581C74520CED60100F4AD34 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 6581C73220CED60000F4AD34 /* Subscribe to Feed */;
|
||||
targetProxy = 6581C74420CED60100F4AD34 /* PBXContainerItemProxy */;
|
||||
};
|
||||
840D61932029031D009BC708 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 840D617B2029031C009BC708 /* Evergreen-iOS */;
|
||||
|
@ -2073,6 +2175,14 @@
|
|||
/* End PBXTargetDependency section */
|
||||
|
||||
/* Begin PBXVariantGroup section */
|
||||
6581C73B20CED60100F4AD34 /* SafariExtensionViewController.xib */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
6581C73C20CED60100F4AD34 /* Base */,
|
||||
);
|
||||
name = SafariExtensionViewController.xib;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
840D61842029031C009BC708 /* Main.storyboard */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
|
@ -2149,6 +2259,30 @@
|
|||
/* End PBXVariantGroup section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
6581C74720CED60100F4AD34 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = D5907CE02002F0FA005947E5 /* Evergreen_target.xcconfig */;
|
||||
buildSettings = {
|
||||
CODE_SIGN_ENTITLEMENTS = "Safari Extension/Subscribe_to_Feed.entitlements";
|
||||
INFOPLIST_FILE = "Safari Extension/Info.plist";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.ranchero.Evergreen.Subscribe-to-Feed";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = macosx;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
6581C74820CED60100F4AD34 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = D5907CE02002F0FA005947E5 /* Evergreen_target.xcconfig */;
|
||||
buildSettings = {
|
||||
CODE_SIGN_ENTITLEMENTS = "Safari Extension/Subscribe_to_Feed.entitlements";
|
||||
INFOPLIST_FILE = "Safari Extension/Info.plist";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.ranchero.Evergreen.Subscribe-to-Feed";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = macosx;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
840D61A42029031E009BC708 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
|
@ -2595,6 +2729,15 @@
|
|||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
6581C75620CED60100F4AD34 /* Build configuration list for PBXNativeTarget "Subscribe to Feed" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
6581C74720CED60100F4AD34 /* Debug */,
|
||||
6581C74820CED60100F4AD34 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
840D61A32029031E009BC708 /* Build configuration list for PBXNativeTarget "Evergreen-iOS" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="11134" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="11134"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<customObject id="-2" userLabel="File's Owner" customClass="SafariExtensionViewController" customModuleProvider="target">
|
||||
<connections>
|
||||
<outlet property="view" destination="c22-O7-iKe" id="vwT-Xx-Aiz"/>
|
||||
</connections>
|
||||
</customObject>
|
||||
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
|
||||
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
|
||||
<customView id="c22-O7-iKe">
|
||||
<rect key="frame" x="0.0" y="0.0" width="330" height="119"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<subviews>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="4Iy-aV-wGF">
|
||||
<rect key="frame" x="103" y="82" width="124" height="17"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Subscribe to Feed" id="2Ec-kd-q2K">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
</subviews>
|
||||
</customView>
|
||||
</objects>
|
||||
</document>
|
|
@ -0,0 +1,64 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>Subscribe to Feed</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>XPC!</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
|
||||
<key>NSExtension</key>
|
||||
<dict>
|
||||
<key>NSExtensionPointIdentifier</key>
|
||||
<string>com.apple.Safari.extension</string>
|
||||
<key>NSExtensionPrincipalClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).SafariExtensionHandler</string>
|
||||
<key>SFSafariContentScript</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>Script</key>
|
||||
<string>evergreen-subscribe-to-feed.js</string>
|
||||
</dict>
|
||||
</array>
|
||||
<key>SFSafariToolbarItem</key>
|
||||
<dict>
|
||||
<key>Action</key>
|
||||
<string>Command</string>
|
||||
<key>Identifier</key>
|
||||
<string>Button</string>
|
||||
<key>Image</key>
|
||||
<string>ToolbarItemIcon.pdf</string>
|
||||
<key>Label</key>
|
||||
<string>Subscribe to Feed</string>
|
||||
</dict>
|
||||
<key>SFSafariWebsiteAccess</key>
|
||||
<dict>
|
||||
<key>Allowed Domains</key>
|
||||
<array>
|
||||
<string>*.*</string>
|
||||
</array>
|
||||
<key>Level</key>
|
||||
<string>All</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
<string>Copyright © 2018 Ranchero Software. All rights reserved.</string>
|
||||
<key>NSHumanReadableDescription</key>
|
||||
<string>This extension adds a Safari toolbar button for easily subscribing to the syndication feed for the current page.</string>
|
||||
</dict>
|
||||
</plist>
|
|
@ -0,0 +1,116 @@
|
|||
//
|
||||
// SafariExtensionHandler.swift
|
||||
// Subscribe to Feed
|
||||
//
|
||||
// Created by Daniel Jalkut on 6/11/18.
|
||||
// Copyright © 2018 Ranchero Software. All rights reserved.
|
||||
//
|
||||
|
||||
import SafariServices
|
||||
|
||||
class SafariExtensionHandler: SFSafariExtensionHandler {
|
||||
|
||||
// Safari App Extensions don't support any reasonable means of detecting whether a
|
||||
// specific Safari page was loaded with the benefit of the extension's injected
|
||||
// JavaScript. For this reason a condition can easily be reached where the toolbar
|
||||
// icon is active for a page, but the expected supporting code is not loaded into
|
||||
// the page. To detect this and disable our icon, we use a kind of "ping" trick
|
||||
// to verify whether our code is installed.
|
||||
|
||||
// I tried to use a NSMapTable from String to the closure directly, but Swift
|
||||
// complains that the object as to be a class type.
|
||||
typealias ValidationHandler = (Bool, String) -> Void
|
||||
class ValidationWrapper {
|
||||
let validationHandler: ValidationHandler
|
||||
|
||||
init(validationHandler: @escaping ValidationHandler) {
|
||||
self.validationHandler = validationHandler
|
||||
}
|
||||
}
|
||||
|
||||
// Maps from UUID to a validation wrapper
|
||||
static var gPingPongMap = Dictionary<String, ValidationWrapper>()
|
||||
|
||||
// Bottleneck for calling through to a validation handler we have saved, and removing it from the list.
|
||||
static func callValidationHandler(forHandlerID handlerID: String, withShouldValidate shouldValidate: Bool) {
|
||||
if let validationWrapper = gPingPongMap[handlerID] {
|
||||
validationWrapper.validationHandler(shouldValidate, "")
|
||||
gPingPongMap.removeValue(forKey: handlerID)
|
||||
}
|
||||
}
|
||||
|
||||
var validationQueue = DispatchQueue(label: "Toolbar Validation")
|
||||
|
||||
override func messageReceived(withName messageName: String, from page: SFSafariPage, userInfo: [String : Any]?) {
|
||||
if (messageName == "subscribeToFeed") {
|
||||
if let feedURLString = userInfo?["url"] as? String {
|
||||
if let feedURL = URL(string: feedURLString) {
|
||||
// We could do something more Evergreen-specific like invoke an app-specific scheme
|
||||
// to subscribe in the app. For starters we just let NSWorkspace open the URL in the
|
||||
// default "feed:" URL scheme handler.
|
||||
NSWorkspace.shared.open(feedURL)
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (messageName == "pong") {
|
||||
if let validationIDString = userInfo?["validationID"] as? String {
|
||||
// Should we validate the button?
|
||||
let shouldValidate = userInfo?["shouldValidate"] as? Bool ?? false
|
||||
SafariExtensionHandler.callValidationHandler(forHandlerID: validationIDString, withShouldValidate:shouldValidate)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override func toolbarItemClicked(in window: SFSafariWindow) {
|
||||
window.getActiveTab { (activeTab) in
|
||||
activeTab?.getActivePage(completionHandler: { (activePage) in
|
||||
activePage?.dispatchMessageToScript(withName: "toolbarButtonClicked", userInfo: nil)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
override func validateToolbarItem(in window: SFSafariWindow, validationHandler: @escaping ((Bool, String) -> Void)) {
|
||||
|
||||
let uniqueValidationID = NSUUID().uuidString
|
||||
|
||||
// Save it right away to eliminate any doubt of whether the handler gets deallocated while
|
||||
// we are waiting for a callback from the getActiveTab or getActivatePage methods below.
|
||||
let validationWrapper = ValidationWrapper(validationHandler: validationHandler)
|
||||
SafariExtensionHandler.gPingPongMap[uniqueValidationID] = validationWrapper
|
||||
|
||||
validationQueue.sync {
|
||||
// See comments above where gPingPongMap is declared. Upon being asked to validate the
|
||||
// toolbar icon for a specific page, we save the validationHandler and postpone calling
|
||||
// it until we have either received a response from our installed JavaScript, or until
|
||||
// a timeout period has elapsed
|
||||
window.getActiveTab { (activeTab) in
|
||||
guard let activeTab = activeTab else {
|
||||
SafariExtensionHandler.callValidationHandler(forHandlerID: uniqueValidationID, withShouldValidate:false);
|
||||
return
|
||||
}
|
||||
|
||||
activeTab.getActivePage { (activePage) in
|
||||
guard let activePage = activePage else {
|
||||
SafariExtensionHandler.callValidationHandler(forHandlerID: uniqueValidationID, withShouldValidate:false);
|
||||
return
|
||||
}
|
||||
|
||||
activePage.getPropertiesWithCompletionHandler { (pageProperties) in
|
||||
if let isActive = pageProperties?.isActive {
|
||||
if isActive {
|
||||
// Capture the uniqueValidationID to ensure it doesn't change out from under us on a future call
|
||||
activePage.dispatchMessageToScript(withName: "ping", userInfo: ["validationID": uniqueValidationID])
|
||||
|
||||
let pongTimeoutInNanoseconds = Int(NSEC_PER_SEC / UInt64(1))
|
||||
let timeoutDeadline = DispatchTime.now() + DispatchTimeInterval.nanoseconds(pongTimeoutInNanoseconds)
|
||||
DispatchQueue.main.asyncAfter(deadline: timeoutDeadline, execute: { [timedOutValidationID = uniqueValidationID] in
|
||||
SafariExtensionHandler.callValidationHandler(forHandlerID: timedOutValidationID, withShouldValidate:false)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
//
|
||||
// SafariExtensionViewController.swift
|
||||
// Subscribe to Feed
|
||||
//
|
||||
// Created by Daniel Jalkut on 6/11/18.
|
||||
// Copyright © 2018 Ranchero Software. All rights reserved.
|
||||
//
|
||||
|
||||
import SafariServices
|
||||
|
||||
class SafariExtensionViewController: SFSafariExtensionViewController {
|
||||
|
||||
// This would be the place to handle a popover that could, for example, list the possibly multiple feeds offered by a site.
|
||||
static let shared: SafariExtensionViewController = {
|
||||
let shared = SafariExtensionViewController()
|
||||
shared.preferredContentSize = NSSize(width:320, height:240)
|
||||
return shared
|
||||
}()
|
||||
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.security.app-sandbox</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,124 @@
|
|||
// Prevent injecting the JavaScript in IFRAMES, and from acting before Safari is ready...
|
||||
if ((window.top === window) && (typeof safari != 'undefined') && (document.location != null)) {
|
||||
document.addEventListener("DOMContentLoaded", function(event) {
|
||||
if (window.top === window)
|
||||
{
|
||||
var thisPageLinkObjects = null;
|
||||
|
||||
// I convert the native "link" node into an object that I can pass out to the global page
|
||||
function objectFromLink(theLink)
|
||||
{
|
||||
var linkObject = new Object();
|
||||
|
||||
linkObject.href = theLink.href;
|
||||
linkObject.type = theLink.type;
|
||||
linkObject.title = theLink.title;
|
||||
|
||||
return linkObject;
|
||||
}
|
||||
|
||||
// Some sites will list feeds with inappropriate or at least less-than-ideal information
|
||||
// in the MIME type attribute. We cover some edge cases here that allow to be passed through,
|
||||
// where they will successfully open as "feed://" URLs in the browser.
|
||||
function isValidFeedLink(theLink)
|
||||
{
|
||||
var isValid = false;
|
||||
|
||||
switch (theLink.type)
|
||||
{
|
||||
case "application/atom+xml":
|
||||
case "application/x.atom+xml":
|
||||
case "application/rss+xml":
|
||||
// These types do not require other criteria.
|
||||
isValid = (theLink.href != null);
|
||||
|
||||
case "text/xml":
|
||||
case "application/rdf+xml":
|
||||
// These types require a title that has "RSS" in it.
|
||||
if (theLink.title && theLink.title.search(/RSS/i) != -1)
|
||||
{
|
||||
isValid = (theLink.href != null);
|
||||
}
|
||||
}
|
||||
|
||||
return isValid;
|
||||
}
|
||||
|
||||
function scanForSyndicationFeeds()
|
||||
{
|
||||
// In case we don't find any, we establish that we have at least tried by setting the
|
||||
// variables to empty instead of null.
|
||||
thisPageLinkObjects = []
|
||||
|
||||
thisPageLinks = document.getElementsByTagName("link");
|
||||
|
||||
for (thisLinkIndex = 0; thisLinkIndex < thisPageLinks.length; thisLinkIndex++)
|
||||
{
|
||||
var thisLink = thisPageLinks[thisLinkIndex];
|
||||
var thisLinkRel = thisLink.getAttribute("rel");
|
||||
if (thisLinkRel == "alternate")
|
||||
{
|
||||
if (isValidFeedLink(thisLink))
|
||||
{
|
||||
thisPageLinkObjects.push(objectFromLink(thisLink));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function subscribeToFeed(theFeed)
|
||||
{
|
||||
// Convert the URL to a feed:// scheme because Safari
|
||||
// will refuse to load e.g. a feed that is listed merely
|
||||
// as "text/xml". We do some preflighting of the link rel
|
||||
// in the PageLoadEnd.js so we can be more confident it's a
|
||||
// good feed: URL.
|
||||
var feedURL = theFeed.href;
|
||||
if (feedURL.match(/^http[s]?:\/\//))
|
||||
{
|
||||
feedURL = feedURL.replace(/^http[s]?:\/\//, "feed://");
|
||||
}
|
||||
else if (feedURL.match(/^feed:/) == false)
|
||||
{
|
||||
feedURL = "feed:" + feedURL;
|
||||
}
|
||||
|
||||
safari.extension.dispatchMessage("subscribeToFeed", { "url": feedURL });
|
||||
}
|
||||
|
||||
safari.self.addEventListener("message", function(event)
|
||||
{
|
||||
if (event.name === "toolbarButtonClicked")
|
||||
{
|
||||
// Workaround Radar #31182842, in which residual copies of our
|
||||
// app extension may remain loaded in context of pages in Safari,
|
||||
// causing multiple responses to broadcast message about toolbar
|
||||
// button being clicked. In the case of the "extra" injections,
|
||||
// the document location is null, so we can avoid doing on anything.
|
||||
if ((document.location != null) && (thisPageLinkObjects.length > 0))
|
||||
{
|
||||
feedToOpen = thisPageLinkObjects[0];
|
||||
subscribeToFeed(feedToOpen);
|
||||
}
|
||||
}
|
||||
else if (event.name === "ping")
|
||||
{
|
||||
// Just a hack to get the toolbar icon validation to work as expected.
|
||||
// If we don't pong back, the extension knows we are not loaded in a page.
|
||||
|
||||
// There is a bug in Safari where the messageHandler is apparently held on to by Safari
|
||||
// even after an extension is disabled. So an effort to "ping" an extension's scripts will
|
||||
// succeed even if its been disabled and the page reloaded. Checking for the existance of
|
||||
// document.location seems to ensure we have enough of a handle still on the document that
|
||||
// we can do something useful with it.
|
||||
var shouldValidate = (document.location != null) && (thisPageLinkObjects.length > 0);
|
||||
|
||||
// Pass back the same validationID we were handed so they can look up the correlated validationHandler
|
||||
safari.extension.dispatchMessage("pong", { "validationID": event.message.validationID, "shouldValidate": thisPageLinkObjects.length > 0 });
|
||||
}
|
||||
}, false);
|
||||
|
||||
scanForSyndicationFeeds();
|
||||
}
|
||||
});
|
||||
}
|
Loading…
Reference in New Issue