Initial import of LogDarioSeq baseline
Establishes version control for the personal Logseq DB fork before starting the server-client architecture migration and domain features described in docs/agent-guide and the approved implementation plan. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
<?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="j1y-V4-xli">
|
||||
<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>
|
||||
<!--Share View Controller-->
|
||||
<scene sceneID="ceB-am-kn3">
|
||||
<objects>
|
||||
<viewController id="j1y-V4-xli" customClass="ShareViewController" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<view key="view" opaque="NO" contentMode="scaleToFill" id="wbc-yd-nQP">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<viewLayoutGuide key="safeArea" id="1Xd-am-t49"/>
|
||||
</view>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="CEy-Cv-SGf" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
</scene>
|
||||
</scenes>
|
||||
</document>
|
||||
35
ios/App/ShareViewController/Info.plist
Normal file
35
ios/App/ShareViewController/Info.plist
Normal file
@@ -0,0 +1,35 @@
|
||||
<?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>NSExtension</key>
|
||||
<dict>
|
||||
<key>NSExtensionAttributes</key>
|
||||
<dict>
|
||||
<key>NSExtensionActivationRule</key>
|
||||
<dict>
|
||||
<key>NSExtensionActivationDictionaryVersion</key>
|
||||
<integer>2</integer>
|
||||
<key>NSExtensionActivationSupportsFileWithMaxCount</key>
|
||||
<integer>5</integer>
|
||||
<key>NSExtensionActivationSupportsImageWithMaxCount</key>
|
||||
<integer>5</integer>
|
||||
<key>NSExtensionActivationSupportsMovieWithMaxCount</key>
|
||||
<integer>5</integer>
|
||||
<key>NSExtensionActivationSupportsText</key>
|
||||
<true/>
|
||||
<key>NSExtensionActivationSupportsWebPageWithMaxCount</key>
|
||||
<integer>3</integer>
|
||||
<key>NSExtensionActivationSupportsWebURLWithMaxCount</key>
|
||||
<integer>3</integer>
|
||||
<key>NSExtensionActivationUsesStrictMatching</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>NSExtensionMainStoryboard</key>
|
||||
<string>MainInterface</string>
|
||||
<key>NSExtensionPointIdentifier</key>
|
||||
<string>com.apple.share-services</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
10
ios/App/ShareViewController/ShareViewController.entitlements
Normal file
10
ios/App/ShareViewController/ShareViewController.entitlements
Normal file
@@ -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.logseq.logseq</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
266
ios/App/ShareViewController/ShareViewController.swift
Normal file
266
ios/App/ShareViewController/ShareViewController.swift
Normal file
@@ -0,0 +1,266 @@
|
||||
//
|
||||
// ShareViewController.swift
|
||||
// ShareViewController
|
||||
//
|
||||
// Created by leizhe on 2022/3/17.
|
||||
//
|
||||
|
||||
|
||||
import MobileCoreServices
|
||||
import Social
|
||||
import UIKit
|
||||
import UniformTypeIdentifiers
|
||||
|
||||
class ShareViewController: UIViewController {
|
||||
|
||||
private var sharedData: SharedData = SharedData.init(resources: [])
|
||||
|
||||
var groupContainerUrl: URL? {
|
||||
return FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.com.logseq.logseq")
|
||||
}
|
||||
|
||||
override public func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(animated)
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
|
||||
self.extensionContext!.completeRequest(returningItems: [], completionHandler: nil)
|
||||
}
|
||||
}
|
||||
|
||||
private func sendData() {
|
||||
let encoder: JSONEncoder = JSONEncoder()
|
||||
let data = try? encoder.encode(self.sharedData)
|
||||
let queryPayload = String(decoding: data!, as: UTF8.self)
|
||||
|
||||
let queryItems =
|
||||
[
|
||||
URLQueryItem(
|
||||
name: "payload",
|
||||
value: queryPayload.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed) ?? ""),
|
||||
]
|
||||
var urlComps = URLComponents(string: "logseq://shared?")!
|
||||
urlComps.queryItems = queryItems
|
||||
openURL(urlComps.url!)
|
||||
}
|
||||
|
||||
fileprivate func createSharedFileUrl(_ url: URL?) -> URL? {
|
||||
let tempFilename = url!
|
||||
.lastPathComponent.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!
|
||||
let copyFileUrl = groupContainerUrl!.appendingPathComponent(tempFilename)
|
||||
try? Data(contentsOf: url!).write(to: copyFileUrl)
|
||||
return copyFileUrl
|
||||
}
|
||||
|
||||
private func saveData(_ data: Data, fileExtension: String) -> URL? {
|
||||
let dateFormatter = DateFormatter()
|
||||
dateFormatter.dateFormat = "yyyy-MM-dd-HH-mm-ss"
|
||||
let filename = dateFormatter.string(from: Date()) + "." + fileExtension
|
||||
|
||||
let copyFileUrl = groupContainerUrl!.appendingPathComponent(filename)
|
||||
do {
|
||||
try data.write(to: copyFileUrl)
|
||||
return copyFileUrl
|
||||
} catch {
|
||||
print(error.localizedDescription)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Screenshots, shared images from some system App are passed as UIImage
|
||||
func saveUIImage(_ image: UIImage) -> URL? {
|
||||
let dateFormatter = DateFormatter()
|
||||
dateFormatter.dateFormat = "yyyy-MM-dd-HH-mm-ss"
|
||||
let filename = dateFormatter.string(from: Date()) + ".png"
|
||||
|
||||
let copyFileUrl = groupContainerUrl!.appendingPathComponent(filename)
|
||||
|
||||
do {
|
||||
try image.pngData()?.write(to: copyFileUrl)
|
||||
return copyFileUrl
|
||||
} catch {
|
||||
print(error.localizedDescription)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Can be a path or a web URL
|
||||
fileprivate func handleTypeUrl(_ attachment: NSItemProvider)
|
||||
async throws -> SharedResource
|
||||
{
|
||||
let results = try await attachment.loadItem(forTypeIdentifier: kUTTypeURL as String, options: nil)
|
||||
let url = results as! URL?
|
||||
|
||||
var res = SharedResource()
|
||||
|
||||
if url!.isFileURL {
|
||||
res.name = url!.lastPathComponent
|
||||
res.ext = url!.pathExtension
|
||||
res.type = url!.pathExtensionAsMimeType()
|
||||
res.url = createSharedFileUrl(url)
|
||||
} else {
|
||||
res.name = url!.absoluteString
|
||||
res.type = "text/plain"
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
// Some shares (notably system screenshots) can come through as a file URL item.
|
||||
fileprivate func handleTypeFileUrl(_ attachment: NSItemProvider)
|
||||
async throws -> SharedResource
|
||||
{
|
||||
let results = try await attachment.loadItem(forTypeIdentifier: kUTTypeFileURL as String, options: nil)
|
||||
let url = results as! URL?
|
||||
|
||||
var res = SharedResource()
|
||||
|
||||
if let url, url.isFileURL {
|
||||
res.name = url.lastPathComponent
|
||||
res.ext = url.pathExtension.lowercased()
|
||||
res.type = url.pathExtensionAsMimeType()
|
||||
res.url = createSharedFileUrl(url)
|
||||
} else if let url {
|
||||
res.name = url.absoluteString
|
||||
res.type = "text/plain"
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
fileprivate func handleTypeText(_ attachment: NSItemProvider)
|
||||
async throws -> SharedResource?
|
||||
{
|
||||
let item = try await attachment.loadItem(forTypeIdentifier: kUTTypeText as String, options: nil)
|
||||
self.sharedData.text = item as? String
|
||||
return nil
|
||||
}
|
||||
|
||||
fileprivate func handleTypeMovie(_ attachment: NSItemProvider)
|
||||
async throws -> SharedResource
|
||||
{
|
||||
let results = try await attachment.loadItem(forTypeIdentifier: kUTTypeMovie as String, options: nil)
|
||||
|
||||
let url = results as! URL?
|
||||
|
||||
let name = url!.lastPathComponent
|
||||
let ext = url!.pathExtension.lowercased()
|
||||
let type = url!.pathExtensionAsMimeType()
|
||||
let sharedUrl = createSharedFileUrl(url)
|
||||
|
||||
let res = SharedResource(name: name, ext: ext, type: type, url: sharedUrl)
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
fileprivate func handleTypeImage(_ attachment: NSItemProvider)
|
||||
async throws -> SharedResource
|
||||
{
|
||||
let data = try await attachment.loadItem(forTypeIdentifier: kUTTypeImage as String, options: nil)
|
||||
|
||||
var res = SharedResource()
|
||||
|
||||
switch data {
|
||||
case let image as UIImage:
|
||||
res.url = self.saveUIImage(image)
|
||||
res.ext = "png"
|
||||
res.name = res.url?.lastPathComponent
|
||||
res.type = res.url?.pathExtensionAsMimeType()
|
||||
case let data as Data:
|
||||
let ext: String
|
||||
if attachment.hasItemConformingToTypeIdentifier(UTType.png.identifier) {
|
||||
ext = "png"
|
||||
} else if attachment.hasItemConformingToTypeIdentifier(UTType.jpeg.identifier) {
|
||||
ext = "jpg"
|
||||
} else if attachment.hasItemConformingToTypeIdentifier(UTType.heic.identifier) {
|
||||
ext = "heic"
|
||||
} else {
|
||||
ext = "png"
|
||||
}
|
||||
|
||||
res.url = self.saveData(data, fileExtension: ext)
|
||||
res.ext = ext
|
||||
res.name = res.url?.lastPathComponent
|
||||
res.type = res.url?.pathExtensionAsMimeType()
|
||||
case let url as URL:
|
||||
res.name = url.lastPathComponent
|
||||
res.ext = url.pathExtension.lowercased()
|
||||
res.type = url.pathExtensionAsMimeType()
|
||||
res.url = self.createSharedFileUrl(url)
|
||||
default:
|
||||
print("Unexpected image data:", type(of: data))
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
|
||||
override public func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
sharedData.empty()
|
||||
let inputItems = extensionContext?.inputItems as! [NSExtensionItem]
|
||||
Task {
|
||||
try await withThrowingTaskGroup(
|
||||
of: SharedResource?.self,
|
||||
body: { taskGroup in
|
||||
for extensionItem in inputItems {
|
||||
for attachment in extensionItem.attachments! {
|
||||
if attachment.hasItemConformingToTypeIdentifier(kUTTypeURL as String) {
|
||||
taskGroup.addTask {
|
||||
return try await self.handleTypeUrl(attachment)
|
||||
}
|
||||
} else if attachment.hasItemConformingToTypeIdentifier(kUTTypeFileURL as String) {
|
||||
taskGroup.addTask {
|
||||
return try await self.handleTypeFileUrl(attachment)
|
||||
}
|
||||
} else if attachment.hasItemConformingToTypeIdentifier(kUTTypeText as String) {
|
||||
taskGroup.addTask {
|
||||
return try await self.handleTypeText(attachment)
|
||||
}
|
||||
} else if attachment.hasItemConformingToTypeIdentifier(kUTTypeMovie as String) {
|
||||
taskGroup.addTask {
|
||||
return try await self.handleTypeMovie(attachment)
|
||||
}
|
||||
} else if attachment.hasItemConformingToTypeIdentifier(kUTTypeImage as String) {
|
||||
taskGroup.addTask {
|
||||
return try await self.handleTypeImage(attachment)
|
||||
}
|
||||
} else {
|
||||
// Useful for diagnosing shares that don't match the legacy kUTType checks.
|
||||
print("Unhandled attachment types:", attachment.registeredTypeIdentifiers)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for try await item in taskGroup {
|
||||
if let item = item {
|
||||
self.sharedData.resources.append(item)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
self.sendData()
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
@objc func openURL(_ url: URL) {
|
||||
var responder: UIResponder? = self
|
||||
while responder != nil {
|
||||
if let application = responder as? UIApplication {
|
||||
application.open(url, options: [:], completionHandler: nil)
|
||||
return
|
||||
}
|
||||
responder = responder?.next
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
extension URL {
|
||||
func pathExtensionAsMimeType() -> String? {
|
||||
let type = UTType(filenameExtension: self.pathExtension)
|
||||
return type?.preferredMIMEType
|
||||
}
|
||||
}
|
||||
25
ios/App/ShareViewController/SharedData.swift
Normal file
25
ios/App/ShareViewController/SharedData.swift
Normal file
@@ -0,0 +1,25 @@
|
||||
//
|
||||
// SharedData.swift
|
||||
// ShareViewController
|
||||
//
|
||||
// Created by Mono Wang on 5/22/23.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public struct SharedResource: Decodable, Encodable {
|
||||
var name: String?
|
||||
var ext: String?
|
||||
var type: String?
|
||||
var url: URL?
|
||||
}
|
||||
|
||||
public struct SharedData: Decodable, Encodable {
|
||||
var text: String?
|
||||
var resources: [SharedResource]
|
||||
|
||||
mutating func empty() {
|
||||
text = nil
|
||||
resources = []
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user