@@ -11,7 +11,8 @@ run: function(parameters) { | |||
}, | |||
finalize: function(parameters) { | |||
var customJavaScript = parameters["customJavaScript"]; | |||
eval(customJavaScript); | |||
} | |||
}; | |||
@@ -0,0 +1,10 @@ | |||
<?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.application-groups</key> | |||
<array> | |||
<string>group.com.abunchtell.writefreely</string> | |||
</array> | |||
</dict> | |||
</plist> |
@@ -1,30 +1,21 @@ | |||
import UIKit | |||
import MobileCoreServices | |||
import UniformTypeIdentifiers | |||
import SwiftUI | |||
class ActionViewController: UIViewController { | |||
@IBOutlet weak var imageView: UIImageView! | |||
let moc = LocalStorageManager.standard.container.viewContext | |||
override var prefersStatusBarHidden: Bool { true } | |||
override func viewDidLoad() { | |||
super.viewDidLoad() | |||
// Get the item[s] we're handling from the extension context. | |||
if let inputItem = extensionContext?.inputItems.first as? NSExtensionItem { | |||
if let itemProvider = inputItem.attachments?.first { | |||
itemProvider.loadItem(forTypeIdentifier: kUTTypePropertyList as String) { [weak self] dict, error in | |||
guard let itemDictionary = dict as? NSDictionary else { return } | |||
guard let javaScriptValues = itemDictionary[NSExtensionJavaScriptPreprocessingResultsKey] as? NSDictionary else { return } | |||
print(javaScriptValues) | |||
} | |||
} | |||
} | |||
} | |||
let contentView = ContentView() | |||
.environment(\.extensionContext, extensionContext) | |||
.environment(\.managedObjectContext, moc) | |||
@IBAction func done() { | |||
// Return any edited content to the host app. | |||
// This template doesn't do anything, so we just echo the passed in items. | |||
self.extensionContext!.completeRequest(returningItems: self.extensionContext!.inputItems, completionHandler: nil) | |||
view = UIHostingView(rootView: contentView) | |||
view.isOpaque = true | |||
view.backgroundColor = .systemBackground | |||
} | |||
} |
@@ -1,53 +0,0 @@ | |||
<?xml version="1.0" encoding="UTF-8"?> | |||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13122.16" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="ObA-dk-sSI"> | |||
<dependencies> | |||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13104.12"/> | |||
<capability name="Safe area layout guides" minToolsVersion="9.0"/> | |||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> | |||
</dependencies> | |||
<scenes> | |||
<!--Image--> | |||
<scene sceneID="7MM-of-jgj"> | |||
<objects> | |||
<viewController title="Image" id="ObA-dk-sSI" customClass="ActionViewController" customModuleProvider="target" sceneMemberID="viewController"> | |||
<view key="view" contentMode="scaleToFill" id="zMn-AG-sqS"> | |||
<rect key="frame" x="0.0" y="0.0" width="320" height="528"/> | |||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> | |||
<subviews> | |||
<navigationBar contentMode="scaleToFill" horizontalCompressionResistancePriority="751" verticalCompressionResistancePriority="751" translatesAutoresizingMaskIntoConstraints="NO" id="NOA-Dm-cuz"> | |||
<items> | |||
<navigationItem id="3HJ-uW-3hn"> | |||
<barButtonItem key="leftBarButtonItem" title="Done" style="done" id="WYi-yp-eM6"> | |||
<connections> | |||
<action selector="done" destination="ObA-dk-sSI" id="Qdu-qn-U6V"/> | |||
</connections> | |||
</barButtonItem> | |||
</navigationItem> | |||
</items> | |||
</navigationBar> | |||
<imageView userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="9ga-4F-77Z"/> | |||
</subviews> | |||
<color key="backgroundColor" xcode11CocoaTouchSystemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/> | |||
<constraints> | |||
<constraint firstItem="VVe-Uw-JpX" firstAttribute="trailing" secondItem="NOA-Dm-cuz" secondAttribute="trailing" id="A05-Pj-hrr"/> | |||
<constraint firstItem="9ga-4F-77Z" firstAttribute="top" secondItem="NOA-Dm-cuz" secondAttribute="bottom" id="Fps-3D-QQW"/> | |||
<constraint firstItem="NOA-Dm-cuz" firstAttribute="leading" secondItem="VVe-Uw-JpX" secondAttribute="leading" id="HxO-8t-aoh"/> | |||
<constraint firstItem="VVe-Uw-JpX" firstAttribute="trailing" secondItem="9ga-4F-77Z" secondAttribute="trailing" id="Ozw-Hg-0yh"/> | |||
<constraint firstItem="9ga-4F-77Z" firstAttribute="leading" secondItem="VVe-Uw-JpX" secondAttribute="leading" id="XH5-ld-ONA"/> | |||
<constraint firstItem="VVe-Uw-JpX" firstAttribute="bottom" secondItem="9ga-4F-77Z" secondAttribute="bottom" id="eQg-nn-Zy4"/> | |||
<constraint firstItem="NOA-Dm-cuz" firstAttribute="top" secondItem="VVe-Uw-JpX" secondAttribute="top" id="we0-1t-bgp"/> | |||
</constraints> | |||
<viewLayoutGuide key="safeArea" id="VVe-Uw-JpX"/> | |||
</view> | |||
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/> | |||
<size key="freeformSize" width="320" height="528"/> | |||
<connections> | |||
<outlet property="imageView" destination="9ga-4F-77Z" id="5y6-5w-9QO"/> | |||
<outlet property="view" destination="zMn-AG-sqS" id="Qma-de-2ek"/> | |||
</connections> | |||
</viewController> | |||
<placeholder placeholderIdentifier="IBFirstResponder" id="X47-rx-isc" userLabel="First Responder" sceneMemberID="firstResponder"/> | |||
</objects> | |||
</scene> | |||
</scenes> | |||
</document> |
@@ -0,0 +1,198 @@ | |||
import SwiftUI | |||
import MobileCoreServices | |||
import UniformTypeIdentifiers | |||
import WriteFreely | |||
enum WFActionExtensionError: Error { | |||
case userCancelledRequest | |||
case couldNotParseInputItems | |||
} | |||
struct ContentView: View { | |||
@Environment(\.extensionContext) private var extensionContext: NSExtensionContext! | |||
@Environment(\.managedObjectContext) private var managedObjectContext | |||
@AppStorage(WFDefaults.defaultFontIntegerKey, store: UserDefaults.shared) var fontIndex: Int = 0 | |||
@FetchRequest( | |||
entity: WFACollection.entity(), | |||
sortDescriptors: [NSSortDescriptor(keyPath: \WFACollection.title, ascending: true)] | |||
) var collections: FetchedResults<WFACollection> | |||
@State private var draftTitle: String = "" | |||
@State private var draftText: String = "" | |||
@State private var isShowingAlert: Bool = false | |||
@State private var selectedBlog: WFACollection? | |||
private var draftsCollectionName: String { | |||
guard UserDefaults.shared.string(forKey: WFDefaults.serverStringKey) == "https://write.as" else { | |||
return "Drafts" | |||
} | |||
return "Anonymous" | |||
} | |||
private var controls: some View { | |||
HStack { | |||
Group { | |||
Button( | |||
action: { extensionContext.cancelRequest(withError: WFActionExtensionError.userCancelledRequest) }, | |||
label: { Image(systemName: "xmark.circle").imageScale(.large) } | |||
) | |||
.accessibilityLabel(Text("Cancel")) | |||
Spacer() | |||
Button( | |||
action: { | |||
savePostToCollection(collection: selectedBlog, title: draftTitle, body: draftText) | |||
extensionContext.completeRequest(returningItems: nil, completionHandler: nil) | |||
}, | |||
label: { Image(systemName: "square.and.arrow.down").imageScale(.large) } | |||
) | |||
.accessibilityLabel(Text("Create new draft")) | |||
} | |||
.padding() | |||
} | |||
} | |||
var body: some View { | |||
VStack { | |||
controls | |||
Form { | |||
Section(header: Text("Title")) { | |||
switch fontIndex { | |||
case 1: | |||
TextField("Draft Title", text: $draftTitle).font(.custom("OpenSans-Regular", size: 26)) | |||
case 2: | |||
TextField("Draft Title", text: $draftTitle).font(.custom("Hack-Regular", size: 26)) | |||
default: | |||
TextField("Draft Title", text: $draftTitle).font(.custom("Lora", size: 26)) | |||
} | |||
} | |||
Section(header: Text("Content")) { | |||
switch fontIndex { | |||
case 1: | |||
TextEditor(text: $draftText).font(.custom("OpenSans-Regular", size: 17)) | |||
case 2: | |||
TextEditor(text: $draftText).font(.custom("Hack-Regular", size: 17)) | |||
default: | |||
TextEditor(text: $draftText).font(.custom("Lora", size: 17)) | |||
} | |||
} | |||
Section(header: Text("Save To")) { | |||
Button(action: { | |||
self.selectedBlog = nil | |||
}, label: { | |||
HStack { | |||
Text(draftsCollectionName) | |||
.foregroundColor(selectedBlog == nil ? .primary : .secondary) | |||
Spacer() | |||
if selectedBlog == nil { | |||
Image(systemName: "checkmark") | |||
} | |||
} | |||
}) | |||
ForEach(collections, id: \.self) { collection in | |||
Button(action: { | |||
self.selectedBlog = collection | |||
}, label: { | |||
HStack { | |||
Text(collection.title) | |||
.foregroundColor(selectedBlog == collection ? .primary : .secondary) | |||
Spacer() | |||
if selectedBlog == collection { | |||
Image(systemName: "checkmark") | |||
} | |||
} | |||
}) | |||
} | |||
} | |||
} | |||
} | |||
.alert(isPresented: $isShowingAlert, content: { | |||
Alert( | |||
title: Text("Something Went Wrong"), | |||
message: Text("WriteFreely can't create a draft with the data received."), | |||
dismissButton: .default(Text("OK"), action: { | |||
extensionContext.cancelRequest(withError: WFActionExtensionError.couldNotParseInputItems) | |||
})) | |||
}) | |||
.onAppear { | |||
do { | |||
try getPageDataFromExtensionContext() | |||
} catch { | |||
self.isShowingAlert = true | |||
} | |||
} | |||
} | |||
private func savePostToCollection(collection: WFACollection?, title: String, body: String) { | |||
let post = WFAPost(context: managedObjectContext) | |||
post.createdDate = Date() | |||
post.title = title | |||
post.body = body | |||
post.status = PostStatus.local.rawValue | |||
post.collectionAlias = collection?.alias | |||
switch fontIndex { | |||
case 1: | |||
post.appearance = "sans" | |||
case 2: | |||
post.appearance = "wrap" | |||
default: | |||
post.appearance = "serif" | |||
} | |||
if let languageCode = Locale.current.languageCode { | |||
post.language = languageCode | |||
post.rtl = Locale.characterDirection(forLanguage: languageCode) == .rightToLeft | |||
} | |||
LocalStorageManager.standard.saveContext() | |||
} | |||
private func getPageDataFromExtensionContext() throws { | |||
if let inputItem = extensionContext.inputItems.first as? NSExtensionItem { | |||
if let itemProvider = inputItem.attachments?.first { | |||
let typeIdentifier: String | |||
if #available(iOS 15, *) { | |||
typeIdentifier = UTType.propertyList.identifier | |||
} else { | |||
typeIdentifier = kUTTypePropertyList as String | |||
} | |||
itemProvider.loadItem(forTypeIdentifier: typeIdentifier) { (dict, error) in | |||
if let error = error { | |||
print("⚠️", error) | |||
self.isShowingAlert = true | |||
} | |||
guard let itemDict = dict as? NSDictionary else { | |||
return | |||
} | |||
guard let jsValues = itemDict[NSExtensionJavaScriptPreprocessingResultsKey] as? NSDictionary else { | |||
return | |||
} | |||
let pageTitle = jsValues["title"] as? String ?? "" | |||
let pageURL = jsValues["URL"] as? String ?? "" | |||
let pageSelectedText = jsValues["selection"] as? String ?? "" | |||
if pageSelectedText.isEmpty { | |||
// If there's no selected text, create a Markdown link to the webpage. | |||
self.draftText = "[\(pageTitle)](\(pageURL))" | |||
} else { | |||
// If there is selected text, create a Markdown blockquote with the selection | |||
// and add a Markdown link to the webpage. | |||
self.draftText = """ | |||
> \(pageSelectedText) | |||
Via: [\(pageTitle)](\(pageURL)) | |||
""" | |||
} | |||
} | |||
} else { | |||
throw WFActionExtensionError.couldNotParseInputItems | |||
} | |||
} else { | |||
throw WFActionExtensionError.couldNotParseInputItems | |||
} | |||
} | |||
} |
@@ -2,6 +2,30 @@ | |||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> | |||
<plist version="1.0"> | |||
<dict> | |||
<key>UIAppFonts</key> | |||
<array> | |||
<string>LoraGX.ttf</string> | |||
<string>OpenSans-Regular.ttf</string> | |||
<string>Hack-Regular.ttf</string> | |||
</array> | |||
<key>CFBundleDevelopmentRegion</key> | |||
<string>$(DEVELOPMENT_LANGUAGE)</string> | |||
<key>CFBundleDisplayName</key> | |||
<string>Create WriteFreely draft</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>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string> | |||
<key>CFBundleShortVersionString</key> | |||
<string>1.0</string> | |||
<key>CFBundleVersion</key> | |||
<string>1</string> | |||
<key>NSExtension</key> | |||
<dict> | |||
<key>NSExtensionAttributes</key> | |||
@@ -24,8 +48,8 @@ | |||
<key>NSExtensionServiceTouchBarIconName</key> | |||
<string>NSActionTemplate</string> | |||
</dict> | |||
<key>NSExtensionMainStoryboard</key> | |||
<string>MainInterface</string> | |||
<key>NSExtensionPrincipalClass</key> | |||
<string>$(PRODUCT_MODULE_NAME).ActionViewController</string> | |||
<key>NSExtensionPointIdentifier</key> | |||
<string>com.apple.ui-services</string> | |||
</dict> | |||
@@ -0,0 +1,100 @@ | |||
{ | |||
"images" : [ | |||
{ | |||
"idiom" : "iphone", | |||
"scale" : "2x", | |||
"size" : "20x20" | |||
}, | |||
{ | |||
"idiom" : "iphone", | |||
"scale" : "3x", | |||
"size" : "20x20" | |||
}, | |||
{ | |||
"idiom" : "iphone", | |||
"scale" : "2x", | |||
"size" : "29x29" | |||
}, | |||
{ | |||
"idiom" : "iphone", | |||
"scale" : "3x", | |||
"size" : "29x29" | |||
}, | |||
{ | |||
"idiom" : "iphone", | |||
"scale" : "2x", | |||
"size" : "40x40" | |||
}, | |||
{ | |||
"idiom" : "iphone", | |||
"scale" : "3x", | |||
"size" : "40x40" | |||
}, | |||
{ | |||
"filename" : "AppIconExtension@2x.png", | |||
"idiom" : "iphone", | |||
"scale" : "2x", | |||
"size" : "60x60" | |||
}, | |||
{ | |||
"filename" : "AppIconExtension@3x.png", | |||
"idiom" : "iphone", | |||
"scale" : "3x", | |||
"size" : "60x60" | |||
}, | |||
{ | |||
"idiom" : "ipad", | |||
"scale" : "1x", | |||
"size" : "20x20" | |||
}, | |||
{ | |||
"idiom" : "ipad", | |||
"scale" : "2x", | |||
"size" : "20x20" | |||
}, | |||
{ | |||
"idiom" : "ipad", | |||
"scale" : "1x", | |||
"size" : "29x29" | |||
}, | |||
{ | |||
"idiom" : "ipad", | |||
"scale" : "2x", | |||
"size" : "29x29" | |||
}, | |||
{ | |||
"idiom" : "ipad", | |||
"scale" : "1x", | |||
"size" : "40x40" | |||
}, | |||
{ | |||
"idiom" : "ipad", | |||
"scale" : "2x", | |||
"size" : "40x40" | |||
}, | |||
{ | |||
"idiom" : "ipad", | |||
"scale" : "1x", | |||
"size" : "76x76" | |||
}, | |||
{ | |||
"idiom" : "ipad", | |||
"scale" : "2x", | |||
"size" : "76x76" | |||
}, | |||
{ | |||
"idiom" : "ipad", | |||
"scale" : "2x", | |||
"size" : "83.5x83.5" | |||
}, | |||
{ | |||
"idiom" : "ios-marketing", | |||
"scale" : "1x", | |||
"size" : "1024x1024" | |||
} | |||
], | |||
"info" : { | |||
"author" : "xcode", | |||
"version" : 1 | |||
} | |||
} |
@@ -56,8 +56,6 @@ extension AccountError: LocalizedError { | |||
struct AccountModel { | |||
@AppStorage(WFDefaults.isLoggedIn, store: UserDefaults.shared) var isLoggedIn: Bool = false | |||
private let defaults = UserDefaults.shared | |||
let usernameStringKey = "usernameStringKey" | |||
let serverStringKey = "serverStringKey" | |||
var server: String = "" | |||
var username: String = "" | |||
@@ -68,19 +66,19 @@ struct AccountModel { | |||
self.user = user | |||
self.username = user.username ?? "" | |||
self.isLoggedIn = true | |||
defaults.set(user.username, forKey: usernameStringKey) | |||
defaults.set(server, forKey: serverStringKey) | |||
defaults.set(user.username, forKey: WFDefaults.usernameStringKey) | |||
defaults.set(server, forKey: WFDefaults.serverStringKey) | |||
} | |||
mutating func logout() { | |||
self.user = nil | |||
self.isLoggedIn = false | |||
defaults.removeObject(forKey: usernameStringKey) | |||
defaults.removeObject(forKey: serverStringKey) | |||
defaults.removeObject(forKey: WFDefaults.usernameStringKey) | |||
defaults.removeObject(forKey: WFDefaults.serverStringKey) | |||
} | |||
mutating func restoreState() { | |||
server = defaults.string(forKey: serverStringKey) ?? "" | |||
username = defaults.string(forKey: usernameStringKey) ?? "" | |||
server = defaults.string(forKey: WFDefaults.serverStringKey) ?? "" | |||
username = defaults.string(forKey: WFDefaults.usernameStringKey) ?? "" | |||
} | |||
} |
@@ -5,6 +5,10 @@ enum WFDefaults { | |||
static let showAllPostsFlag = "showAllPostsFlag" | |||
static let selectedCollectionURL = "selectedCollectionURL" | |||
static let lastDraftURL = "lastDraftURL" | |||
static let colorSchemeIntegerKey = "colorSchemeIntegerKey" | |||
static let defaultFontIntegerKey = "defaultFontIntegerKey" | |||
static let usernameStringKey = "usernameStringKey" | |||
static let serverStringKey = "serverStringKey" | |||
#if os(macOS) | |||
static let automaticallyChecksForUpdates = "automaticallyChecksForUpdates" | |||
static let subscribeToBetaUpdates = "subscribeToBetaUpdates" | |||
@@ -43,8 +43,8 @@ final class WriteFreelyModel: ObservableObject { | |||
init() { | |||
DispatchQueue.main.async { | |||
self.preferences.appearance = self.defaults.integer(forKey: self.preferences.colorSchemeIntegerKey) | |||
self.preferences.font = self.defaults.integer(forKey: self.preferences.defaultFontIntegerKey) | |||
self.preferences.appearance = self.defaults.integer(forKey: WFDefaults.colorSchemeIntegerKey) | |||
self.preferences.font = self.defaults.integer(forKey: WFDefaults.defaultFontIntegerKey) | |||
self.account.restoreState() | |||
if self.account.isLoggedIn { | |||
guard let serverURL = URL(string: self.account.server) else { | |||
@@ -2,8 +2,6 @@ import SwiftUI | |||
class PreferencesModel: ObservableObject { | |||
private let defaults = UserDefaults.shared | |||
let colorSchemeIntegerKey = "colorSchemeIntegerKey" | |||
let defaultFontIntegerKey = "defaultFontIntegerKey" | |||
/* We're stuck dropping into AppKit/UIKit to set light/dark schemes for now, | |||
* because setting the .preferredColorScheme modifier on views in SwiftUI is | |||
@@ -17,6 +15,7 @@ class PreferencesModel: ObservableObject { | |||
*/ | |||
#if os(iOS) | |||
@available(iOSApplicationExtension, unavailable) | |||
var window: UIWindow? { | |||
guard let scene = UIApplication.shared.connectedScenes.first, | |||
let windowSceneDelegate = scene.delegate as? UIWindowSceneDelegate, | |||
@@ -27,7 +26,10 @@ class PreferencesModel: ObservableObject { | |||
} | |||
#endif | |||
@available(iOSApplicationExtension, unavailable) | |||
@Published var selectedColorScheme: ColorScheme? | |||
@available(iOSApplicationExtension, unavailable) | |||
@Published var appearance: Int = 0 { | |||
didSet { | |||
switch appearance { | |||
@@ -54,12 +56,12 @@ class PreferencesModel: ObservableObject { | |||
#endif | |||
} | |||
defaults.set(appearance, forKey: colorSchemeIntegerKey) | |||
defaults.set(appearance, forKey: WFDefaults.colorSchemeIntegerKey) | |||
} | |||
} | |||
@Published var font: Int = 0 { | |||
didSet { | |||
defaults.set(font, forKey: defaultFontIntegerKey) | |||
defaults.set(font, forKey: WFDefaults.defaultFontIntegerKey) | |||
} | |||
} | |||
} |
@@ -30,9 +30,21 @@ | |||
172E10012735B83E00061372 /* UniformTypeIdentifiers.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 172E10002735B83E00061372 /* UniformTypeIdentifiers.framework */; platformFilter = maccatalyst; }; | |||
172E10042735B83E00061372 /* Media.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 172E10032735B83E00061372 /* Media.xcassets */; }; | |||
172E10062735B83E00061372 /* ActionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 172E10052735B83E00061372 /* ActionViewController.swift */; }; | |||
172E10092735B83E00061372 /* MainInterface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 172E10072735B83E00061372 /* MainInterface.storyboard */; }; | |||
172E100D2735B83E00061372 /* ActionExtension-iOS.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 172E0FFF2735B83E00061372 /* ActionExtension-iOS.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; | |||
172E10132735BB6200061372 /* Action.js in Resources */ = {isa = PBXBuildFile; fileRef = 172E10122735BB6200061372 /* Action.js */; }; | |||
172E10152735C2BD00061372 /* UIHostingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 172E10142735C2BD00061372 /* UIHostingView.swift */; }; | |||
172E10172735C2DF00061372 /* EnvironmentValues+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 172E10162735C2DF00061372 /* EnvironmentValues+Extensions.swift */; }; | |||
172E10192735C3DB00061372 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 172E10182735C3DB00061372 /* ContentView.swift */; }; | |||
172E101B2735C54400061372 /* WriteFreely in Frameworks */ = {isa = PBXBuildFile; productRef = 172E101A2735C54400061372 /* WriteFreely */; }; | |||
172E101C2735C57400061372 /* LocalStorageManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1756DBB924FED45500207AB8 /* LocalStorageManager.swift */; }; | |||
172E101D2735C5AB00061372 /* LocalStorageModel.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 1756DBB524FED3A400207AB8 /* LocalStorageModel.xcdatamodeld */; }; | |||
172E101E2735C62F00061372 /* PostStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17C42E612507D8E600072984 /* PostStatus.swift */; }; | |||
172E101F2735C64600061372 /* WFAPost+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17B996D62502D23E0017B536 /* WFAPost+CoreDataClass.swift */; }; | |||
172E10202735C64600061372 /* WFACollection+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1756DBFF24FEE18400207AB8 /* WFACollection+CoreDataClass.swift */; }; | |||
172E10212735C64600061372 /* WFACollection+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1756DC0024FEE18400207AB8 /* WFACollection+CoreDataProperties.swift */; }; | |||
172E10222735C64600061372 /* WFAPost+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17B996D72502D23E0017B536 /* WFAPost+CoreDataProperties.swift */; }; | |||
172E10232735C6FF00061372 /* NSManagedObjectContext+ExecuteAndMergeChanges.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17C42E6F250AA12200072984 /* NSManagedObjectContext+ExecuteAndMergeChanges.swift */; }; | |||
172E10242735C72500061372 /* PreferencesModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17D435E724E3128F0036B539 /* PreferencesModel.swift */; }; | |||
173E19D1254318F600440F0F /* RemoteChangePromptView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 173E19D0254318F600440F0F /* RemoteChangePromptView.swift */; }; | |||
173E19E3254329CC00440F0F /* PostTextEditingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 173E19E2254329CC00440F0F /* PostTextEditingView.swift */; }; | |||
17466626256C0D0600629997 /* MacEditorTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17466625256C0D0600629997 /* MacEditorTextView.swift */; }; | |||
@@ -62,6 +74,10 @@ | |||
1756DC0424FEE18400207AB8 /* WFACollection+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1756DC0024FEE18400207AB8 /* WFACollection+CoreDataProperties.swift */; }; | |||
17681E412519410E00D394AE /* UINavigationController+Appearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17681E402519410E00D394AE /* UINavigationController+Appearance.swift */; }; | |||
1780F6EF25895EDB00FE45FF /* PostCommands.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1780F6EE25895EDB00FE45FF /* PostCommands.swift */; }; | |||
17836C14273EFB870047AF61 /* UserDefaults+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 171DC676272C7D0B002B9B8A /* UserDefaults+Extensions.swift */; }; | |||
17836C15273F0FBB0047AF61 /* Hack-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 17D4F3A42514F1E900517CE6 /* Hack-Regular.ttf */; }; | |||
17836C16273F0FBB0047AF61 /* LoraGX.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 17D4F36B2514EE2F00517CE6 /* LoraGX.ttf */; }; | |||
17836C17273F0FBB0047AF61 /* OpenSans-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 17D4F39D2514F0E500517CE6 /* OpenSans-Regular.ttf */; }; | |||
17A4FEDA25924AF70037E96B /* Sparkle in Frameworks */ = {isa = PBXBuildFile; productRef = 17A4FED925924AF70037E96B /* Sparkle */; }; | |||
17A4FEED25927E730037E96B /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17A4FEEC25927E730037E96B /* AppDelegate.swift */; }; | |||
17A5388824DDA31F00DEFF9A /* MacAccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17A5388724DDA31F00DEFF9A /* MacAccountView.swift */; }; | |||
@@ -170,9 +186,11 @@ | |||
172E10002735B83E00061372 /* UniformTypeIdentifiers.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UniformTypeIdentifiers.framework; path = System/Library/Frameworks/UniformTypeIdentifiers.framework; sourceTree = SDKROOT; }; | |||
172E10032735B83E00061372 /* Media.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Media.xcassets; sourceTree = "<group>"; }; | |||
172E10052735B83E00061372 /* ActionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionViewController.swift; sourceTree = "<group>"; }; | |||
172E10082735B83E00061372 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/MainInterface.storyboard; sourceTree = "<group>"; }; | |||
172E100A2735B83E00061372 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; | |||
172E100A2735B83E00061372 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; usesTabs = 1; }; | |||
172E10122735BB6200061372 /* Action.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; path = Action.js; sourceTree = "<group>"; }; | |||
172E10142735C2BD00061372 /* UIHostingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIHostingView.swift; sourceTree = "<group>"; }; | |||
172E10162735C2DF00061372 /* EnvironmentValues+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "EnvironmentValues+Extensions.swift"; sourceTree = "<group>"; }; | |||
172E10182735C3DB00061372 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; }; | |||
173E19D0254318F600440F0F /* RemoteChangePromptView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteChangePromptView.swift; sourceTree = "<group>"; }; | |||
173E19E2254329CC00440F0F /* PostTextEditingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostTextEditingView.swift; sourceTree = "<group>"; }; | |||
17466625256C0D0600629997 /* MacEditorTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MacEditorTextView.swift; sourceTree = "<group>"; }; | |||
@@ -192,6 +210,7 @@ | |||
1756DC0024FEE18400207AB8 /* WFACollection+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WFACollection+CoreDataProperties.swift"; sourceTree = SOURCE_ROOT; }; | |||
17681E402519410E00D394AE /* UINavigationController+Appearance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UINavigationController+Appearance.swift"; sourceTree = "<group>"; }; | |||
1780F6EE25895EDB00FE45FF /* PostCommands.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostCommands.swift; sourceTree = "<group>"; }; | |||
17836C18273F10C40047AF61 /* ActionExtension-iOS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "ActionExtension-iOS.entitlements"; sourceTree = "<group>"; }; | |||
17A355D3271A052C007C7A47 /* WriteFreely-MultiPlatform (iOS).entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "WriteFreely-MultiPlatform (iOS).entitlements"; sourceTree = "<group>"; }; | |||
17A4FEDF25924E810037E96B /* MacSoftwareUpdater.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = MacSoftwareUpdater.md; sourceTree = "<group>"; }; | |||
17A4FEEC25927E730037E96B /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; }; | |||
@@ -250,6 +269,7 @@ | |||
buildActionMask = 2147483647; | |||
files = ( | |||
172E10012735B83E00061372 /* UniformTypeIdentifiers.framework in Frameworks */, | |||
172E101B2735C54400061372 /* WriteFreely in Frameworks */, | |||
); | |||
runOnlyForDeploymentPostprocessing = 0; | |||
}; | |||
@@ -308,9 +328,10 @@ | |||
172E10022735B83E00061372 /* ActionExtension-iOS */ = { | |||
isa = PBXGroup; | |||
children = ( | |||
17836C18273F10C40047AF61 /* ActionExtension-iOS.entitlements */, | |||
172E10032735B83E00061372 /* Media.xcassets */, | |||
172E10182735C3DB00061372 /* ContentView.swift */, | |||
172E10052735B83E00061372 /* ActionViewController.swift */, | |||
172E10072735B83E00061372 /* MainInterface.storyboard */, | |||
172E100A2735B83E00061372 /* Info.plist */, | |||
172E10122735BB6200061372 /* Action.js */, | |||
); | |||
@@ -367,6 +388,8 @@ | |||
children = ( | |||
1756AE8024CB844500FD7257 /* View+Keyboard.swift */, | |||
17681E402519410E00D394AE /* UINavigationController+Appearance.swift */, | |||
172E10142735C2BD00061372 /* UIHostingView.swift */, | |||
172E10162735C2DF00061372 /* EnvironmentValues+Extensions.swift */, | |||
); | |||
path = Extensions; | |||
sourceTree = "<group>"; | |||
@@ -595,6 +618,9 @@ | |||
dependencies = ( | |||
); | |||
name = "ActionExtension-iOS"; | |||
packageProductDependencies = ( | |||
172E101A2735C54400061372 /* WriteFreely */, | |||
); | |||
productName = "ActionExtension-iOS"; | |||
productReference = 172E0FFF2735B83E00061372 /* ActionExtension-iOS.appex */; | |||
productType = "com.apple.product-type.app-extension"; | |||
@@ -739,9 +765,11 @@ | |||
isa = PBXResourcesBuildPhase; | |||
buildActionMask = 2147483647; | |||
files = ( | |||
17836C17273F0FBB0047AF61 /* OpenSans-Regular.ttf in Resources */, | |||
172E10132735BB6200061372 /* Action.js in Resources */, | |||
172E10042735B83E00061372 /* Media.xcassets in Resources */, | |||
172E10092735B83E00061372 /* MainInterface.storyboard in Resources */, | |||
17836C15273F0FBB0047AF61 /* Hack-Regular.ttf in Resources */, | |||
17836C16273F0FBB0047AF61 /* LoraGX.ttf in Resources */, | |||
); | |||
runOnlyForDeploymentPostprocessing = 0; | |||
}; | |||
@@ -834,6 +862,19 @@ | |||
buildActionMask = 2147483647; | |||
files = ( | |||
172E10062735B83E00061372 /* ActionViewController.swift in Sources */, | |||
172E10202735C64600061372 /* WFACollection+CoreDataClass.swift in Sources */, | |||
172E10222735C64600061372 /* WFAPost+CoreDataProperties.swift in Sources */, | |||
172E101D2735C5AB00061372 /* LocalStorageModel.xcdatamodeld in Sources */, | |||
17836C14273EFB870047AF61 /* UserDefaults+Extensions.swift in Sources */, | |||
172E10242735C72500061372 /* PreferencesModel.swift in Sources */, | |||
172E10172735C2DF00061372 /* EnvironmentValues+Extensions.swift in Sources */, | |||
172E10212735C64600061372 /* WFACollection+CoreDataProperties.swift in Sources */, | |||
172E101C2735C57400061372 /* LocalStorageManager.swift in Sources */, | |||
172E10192735C3DB00061372 /* ContentView.swift in Sources */, | |||
172E10152735C2BD00061372 /* UIHostingView.swift in Sources */, | |||
172E101F2735C64600061372 /* WFAPost+CoreDataClass.swift in Sources */, | |||
172E10232735C6FF00061372 /* NSManagedObjectContext+ExecuteAndMergeChanges.swift in Sources */, | |||
172E101E2735C62F00061372 /* PostStatus.swift in Sources */, | |||
); | |||
runOnlyForDeploymentPostprocessing = 0; | |||
}; | |||
@@ -968,22 +1009,14 @@ | |||
}; | |||
/* End PBXTargetDependency section */ | |||
/* Begin PBXVariantGroup section */ | |||
172E10072735B83E00061372 /* MainInterface.storyboard */ = { | |||
isa = PBXVariantGroup; | |||
children = ( | |||
172E10082735B83E00061372 /* Base */, | |||
); | |||
name = MainInterface.storyboard; | |||
sourceTree = "<group>"; | |||
}; | |||
/* End PBXVariantGroup section */ | |||
/* Begin XCBuildConfiguration section */ | |||
172E100F2735B83E00061372 /* Debug */ = { | |||
isa = XCBuildConfiguration; | |||
buildSettings = { | |||
ASSETCATALOG_COMPILER_ALTERNATE_APPICON_NAMES = ""; | |||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIconExtension; | |||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; | |||
CODE_SIGN_ENTITLEMENTS = "ActionExtension-iOS/ActionExtension-iOS.entitlements"; | |||
CODE_SIGN_STYLE = Automatic; | |||
CURRENT_PROJECT_VERSION = 1; | |||
DEVELOPMENT_TEAM = TPPAB4YBA6; | |||
@@ -991,7 +1024,7 @@ | |||
INFOPLIST_FILE = "ActionExtension-iOS/Info.plist"; | |||
INFOPLIST_KEY_CFBundleDisplayName = "ActionExtension-iOS"; | |||
INFOPLIST_KEY_NSHumanReadableCopyright = ""; | |||
IPHONEOS_DEPLOYMENT_TARGET = 15.0; | |||
IPHONEOS_DEPLOYMENT_TARGET = 14.0; | |||
LD_RUNPATH_SEARCH_PATHS = ( | |||
"$(inherited)", | |||
"@executable_path/Frameworks", | |||
@@ -1011,7 +1044,10 @@ | |||
172E10102735B83E00061372 /* Release */ = { | |||
isa = XCBuildConfiguration; | |||
buildSettings = { | |||
ASSETCATALOG_COMPILER_ALTERNATE_APPICON_NAMES = ""; | |||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIconExtension; | |||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; | |||
CODE_SIGN_ENTITLEMENTS = "ActionExtension-iOS/ActionExtension-iOS.entitlements"; | |||
CODE_SIGN_STYLE = Automatic; | |||
CURRENT_PROJECT_VERSION = 1; | |||
DEVELOPMENT_TEAM = TPPAB4YBA6; | |||
@@ -1019,7 +1055,7 @@ | |||
INFOPLIST_FILE = "ActionExtension-iOS/Info.plist"; | |||
INFOPLIST_KEY_CFBundleDisplayName = "ActionExtension-iOS"; | |||
INFOPLIST_KEY_NSHumanReadableCopyright = ""; | |||
IPHONEOS_DEPLOYMENT_TARGET = 15.0; | |||
IPHONEOS_DEPLOYMENT_TARGET = 14.0; | |||
LD_RUNPATH_SEARCH_PATHS = ( | |||
"$(inherited)", | |||
"@executable_path/Frameworks", | |||
@@ -1431,6 +1467,11 @@ | |||
package = 1714DD63260BAC14000C0DFF /* XCRemoteSwiftPackageReference "writefreely-swift" */; | |||
productName = WriteFreely; | |||
}; | |||
172E101A2735C54400061372 /* WriteFreely */ = { | |||
isa = XCSwiftPackageProductDependency; | |||
package = 1714DD63260BAC14000C0DFF /* XCRemoteSwiftPackageReference "writefreely-swift" */; | |||
productName = WriteFreely; | |||
}; | |||
17A4FED925924AF70037E96B /* Sparkle */ = { | |||
isa = XCSwiftPackageProductDependency; | |||
package = 17A4FED825924AF70037E96B /* XCRemoteSwiftPackageReference "Sparkle" */; | |||
@@ -7,7 +7,7 @@ | |||
<key>ActionExtension-iOS.xcscheme_^#shared#^_</key> | |||
<dict> | |||
<key>orderHint</key> | |||
<integer>2</integer> | |||
<integer>1</integer> | |||
</dict> | |||
<key>WriteFreely-MultiPlatform (iOS).xcscheme_^#shared#^_</key> | |||
<dict> | |||
@@ -17,7 +17,7 @@ | |||
<key>WriteFreely-MultiPlatform (macOS).xcscheme_^#shared#^_</key> | |||
<dict> | |||
<key>orderHint</key> | |||
<integer>1</integer> | |||
<integer>2</integer> | |||
</dict> | |||
</dict> | |||
</dict> | |||
@@ -0,0 +1,20 @@ | |||
// Credit: | |||
// https://github.com/sindresorhus/Blear/blob/9ce7cd6ad8d6a88f8d0be12b1ef9152baeeacf96/Blear/Utilities.swift#L1052-L1064 | |||
import SwiftUI | |||
extension EnvironmentValues { | |||
private struct ExtensionContext: EnvironmentKey { | |||
static var defaultValue: NSExtensionContext? | |||
} | |||
/// The `.extensionContext` of an app extension view controller. | |||
var extensionContext: NSExtensionContext? { | |||
get { self[ExtensionContext.self] } | |||
set { | |||
self[ExtensionContext.self] = newValue | |||
} | |||
} | |||
} |
@@ -0,0 +1,57 @@ | |||
// Credit: | |||
// https://github.com/sindresorhus/Blear/blob/9ce7cd6ad8d6a88f8d0be12b1ef9152baeeacf96/Blear/Utilities.swift#L317-L368 | |||
import SwiftUI | |||
final class UIHostingView<Content: View>: UIView { | |||
private let rootViewHostingController: UIHostingController<Content> | |||
var rootView: Content { | |||
get { rootViewHostingController.rootView } | |||
set { | |||
rootViewHostingController.rootView = newValue | |||
} | |||
} | |||
required init(rootView: Content) { | |||
self.rootViewHostingController = UIHostingController(rootView: rootView) | |||
super.init(frame: .zero) | |||
rootViewHostingController.view.backgroundColor = .clear | |||
addSubview(rootViewHostingController.view) | |||
} | |||
@available(*, unavailable) | |||
required init?(coder: NSCoder) { | |||
fatalError("init(coder:) has not been implemented") | |||
} | |||
override func layoutSubviews() { | |||
super.layoutSubviews() | |||
rootViewHostingController.view.frame = bounds | |||
} | |||
override func sizeToFit() { | |||
guard let superview = superview else { | |||
super.sizeToFit() | |||
return | |||
} | |||
frame.size = rootViewHostingController.sizeThatFits(in: superview.frame.size) | |||
} | |||
override func sizeThatFits(_ size: CGSize) -> CGSize { | |||
rootViewHostingController.sizeThatFits(in: size) | |||
} | |||
override func systemLayoutSizeFitting(_ targetSize: CGSize) -> CGSize { | |||
rootViewHostingController.sizeThatFits(in: targetSize) | |||
} | |||
override func systemLayoutSizeFitting( | |||
_ targetSize: CGSize, | |||
withHorizontalFittingPriority horizontalFittingPriority: UILayoutPriority, | |||
verticalFittingPriority: UILayoutPriority | |||
) -> CGSize { | |||
rootViewHostingController.sizeThatFits(in: targetSize) | |||
} | |||
} |