mirror of
https://github.com/logseq/logseq.git
synced 2026-05-27 22:24:09 +00:00
178 lines
5.6 KiB
Swift
178 lines
5.6 KiB
Swift
import Foundation
|
|
import Capacitor
|
|
import SwiftUI
|
|
import WebKit
|
|
|
|
@objc(LiquidTabsPlugin)
|
|
public class LiquidTabsPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
// So SwiftUI can notify JS
|
|
static weak var shared: LiquidTabsPlugin?
|
|
|
|
private let store = LiquidTabsStore.shared
|
|
private var keyboardHackScriptInstalled = false
|
|
private let keyboardHackHandlerName = "keyboardHackKey"
|
|
|
|
public let identifier = "LiquidTabsPlugin"
|
|
public let jsName = "LiquidTabsPlugin"
|
|
public let pluginMethods: [CAPPluginMethod] = [
|
|
CAPPluginMethod(name: "configureTabs", returnType: CAPPluginReturnPromise),
|
|
CAPPluginMethod(name: "selectTab", returnType: CAPPluginReturnPromise),
|
|
CAPPluginMethod(name: "updateNativeSearchResults", returnType: CAPPluginReturnPromise),
|
|
]
|
|
|
|
public override func load() {
|
|
super.load()
|
|
LiquidTabsPlugin.shared = self
|
|
installKeyboardHackScript()
|
|
}
|
|
|
|
// MARK: - Methods from JS
|
|
|
|
/// Configure tabs from JS.
|
|
/// Expected args:
|
|
/// { tabs: [{ id, title, systemImage, role }] }
|
|
@objc func configureTabs(_ call: CAPPluginCall) {
|
|
guard let tabsArray = call.getArray("tabs") as? [[String: Any]] else {
|
|
call.reject("Missing 'tabs'")
|
|
return
|
|
}
|
|
|
|
let tabs: [LiquidTab] = tabsArray.compactMap { dict in
|
|
guard
|
|
let id = dict["id"] as? String,
|
|
let title = dict["title"] as? String
|
|
else { return nil }
|
|
|
|
let rawSystemImage = dict["systemImage"] as? String ?? "square"
|
|
|
|
let systemImage: String = {
|
|
if UIImage(systemName: rawSystemImage) != nil {
|
|
return rawSystemImage
|
|
} else {
|
|
return "square"
|
|
}
|
|
}()
|
|
|
|
let roleStr = dict["role"] as? String ?? "normal"
|
|
let role: LiquidTab.Role = (roleStr == "search") ? .search : .normal
|
|
|
|
return LiquidTab(id: id, title: title, systemImage: systemImage, role: role)
|
|
}
|
|
|
|
DispatchQueue.main.async {
|
|
self.store.tabs = tabs
|
|
if let firstId = tabs.first?.id {
|
|
self.store.selectedId = firstId
|
|
}
|
|
}
|
|
|
|
call.resolve()
|
|
}
|
|
|
|
/// Programmatically select a tab by id.
|
|
/// { id: string }
|
|
@objc func selectTab(_ call: CAPPluginCall) {
|
|
guard let id = call.getString("id") else {
|
|
call.reject("Missing 'id'")
|
|
return
|
|
}
|
|
|
|
DispatchQueue.main.async {
|
|
self.store.selectedId = id
|
|
}
|
|
|
|
call.resolve()
|
|
}
|
|
|
|
/// Update native search results list from JS.
|
|
/// { results: [{ id, title, subtitle? }] }
|
|
@objc func updateNativeSearchResults(_ call: CAPPluginCall) {
|
|
guard let resultDicts = call.getArray("results", JSObject.self) else {
|
|
call.reject("Missing 'results'")
|
|
return
|
|
}
|
|
|
|
let mapped: [NativeSearchResult] = resultDicts.compactMap { dict in
|
|
guard let id = dict["id"] as? String,
|
|
let title = dict["title"] as? String else {
|
|
return nil
|
|
}
|
|
let subtitle = dict["subtitle"] as? String
|
|
return NativeSearchResult(id: id, title: title, subtitle: subtitle)
|
|
}
|
|
|
|
store.updateSearchResults(mapped)
|
|
call.resolve()
|
|
}
|
|
|
|
// MARK: - Events to JS
|
|
|
|
func notifyTabSelected(id: String) {
|
|
notifyListeners("tabSelected", data: ["id": id])
|
|
}
|
|
|
|
func notifySearchChanged(query: String) {
|
|
notifyListeners("searchChanged", data: ["query": query])
|
|
}
|
|
|
|
func notifyKeyboardHackKey(key: String) {
|
|
notifyListeners("keyboardHackKey", data: ["key": key])
|
|
}
|
|
|
|
func openResult(id: String) {
|
|
notifyListeners("openSearchResultBlock", data: ["id": id])
|
|
}
|
|
|
|
private func installKeyboardHackScript() {
|
|
guard !keyboardHackScriptInstalled,
|
|
let controller = bridge?.webView?.configuration.userContentController else {
|
|
return
|
|
}
|
|
|
|
keyboardHackScriptInstalled = true
|
|
controller.removeScriptMessageHandler(forName: keyboardHackHandlerName)
|
|
controller.add(self, name: keyboardHackHandlerName)
|
|
|
|
let source = """
|
|
(function() {
|
|
if (window.__logseqKeyboardHackInstalled) return;
|
|
window.__logseqKeyboardHackInstalled = true;
|
|
window.addEventListener('keydown', function(e) {
|
|
var k = null;
|
|
switch (e.key) {
|
|
case 'Backspace':
|
|
k = 'backspace';
|
|
break;
|
|
case 'Enter':
|
|
case 'Return':
|
|
k = 'enter';
|
|
break;
|
|
default:
|
|
if (e.keyCode === 8) k = 'backspace';
|
|
else if (e.keyCode === 13) k = 'enter';
|
|
break;
|
|
}
|
|
if (!k) return;
|
|
try {
|
|
window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.\(keyboardHackHandlerName).postMessage({ key: k });
|
|
} catch (_) {}
|
|
}, true);
|
|
})();
|
|
"""
|
|
|
|
let script = WKUserScript(source: source, injectionTime: .atDocumentStart, forMainFrameOnly: false)
|
|
controller.addUserScript(script)
|
|
}
|
|
}
|
|
|
|
extension LiquidTabsPlugin: WKScriptMessageHandler {
|
|
public func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
|
|
guard message.name == keyboardHackHandlerName else { return }
|
|
|
|
if let body = message.body as? [String: Any],
|
|
let key = body["key"] as? String {
|
|
notifyKeyboardHackKey(key: key)
|
|
}
|
|
}
|
|
}
|