This commit is contained in:
@@ -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>LSSupportsOpeningDocumentsInPlace</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -19,6 +19,8 @@
|
||||
<string>EPUB Document</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Alternate</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LSItemContentTypes</key>
|
||||
<array>
|
||||
<string>org.idpf.epub-container</string>
|
||||
@@ -30,6 +32,8 @@
|
||||
<string>PDF Document</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Alternate</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LSItemContentTypes</key>
|
||||
<array>
|
||||
<string>com.adobe.pdf</string>
|
||||
@@ -41,6 +45,8 @@
|
||||
<string>FB2 Document</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Alternate</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LSItemContentTypes</key>
|
||||
<array>
|
||||
<string>com.readest.fb2</string>
|
||||
@@ -52,6 +58,8 @@
|
||||
<string>CBZ Archive</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Alternate</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LSItemContentTypes</key>
|
||||
<array>
|
||||
<string>com.readest.cbz</string>
|
||||
@@ -63,6 +71,8 @@
|
||||
<string>MOBI Document</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Alternate</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LSItemContentTypes</key>
|
||||
<array>
|
||||
<string>org.mobipocket.mobi</string>
|
||||
@@ -74,6 +84,8 @@
|
||||
<string>AZW Document</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Alternate</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LSItemContentTypes</key>
|
||||
<array>
|
||||
<string>com.amazon.azw</string>
|
||||
@@ -86,6 +98,8 @@
|
||||
<string>Text File</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Alternate</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LSItemContentTypes</key>
|
||||
<array>
|
||||
<string>public.plain-text</string>
|
||||
@@ -93,6 +107,153 @@
|
||||
</dict>
|
||||
</array>
|
||||
|
||||
<key>UTImportedTypeDeclarations</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>UTTypeIdentifier</key>
|
||||
<string>org.idpf.epub-container</string>
|
||||
<key>UTTypeDescription</key>
|
||||
<string>EPUB Document</string>
|
||||
<key>UTTypeConformsTo</key>
|
||||
<array>
|
||||
<string>public.data</string>
|
||||
<string>public.composite-content</string>
|
||||
</array>
|
||||
<key>UTTypeTagSpecification</key>
|
||||
<dict>
|
||||
<key>public.filename-extension</key>
|
||||
<array>
|
||||
<string>epub</string>
|
||||
</array>
|
||||
<key>public.mime-type</key>
|
||||
<string>application/epub+zip</string>
|
||||
</dict>
|
||||
</dict>
|
||||
|
||||
<dict>
|
||||
<key>UTTypeIdentifier</key>
|
||||
<string>com.adobe.pdf</string>
|
||||
<key>UTTypeDescription</key>
|
||||
<string>PDF Document</string>
|
||||
<key>UTTypeConformsTo</key>
|
||||
<array>
|
||||
<string>public.data</string>
|
||||
<string>public.composite-content</string>
|
||||
</array>
|
||||
<key>UTTypeTagSpecification</key>
|
||||
<dict>
|
||||
<key>public.filename-extension</key>
|
||||
<array>
|
||||
<string>pdf</string>
|
||||
</array>
|
||||
<key>public.mime-type</key>
|
||||
<string>application/pdf</string>
|
||||
</dict>
|
||||
</dict>
|
||||
|
||||
<dict>
|
||||
<key>UTTypeIdentifier</key>
|
||||
<string>com.readest.fb2</string>
|
||||
<key>UTTypeDescription</key>
|
||||
<string>FB2 Document</string>
|
||||
<key>UTTypeConformsTo</key>
|
||||
<array>
|
||||
<string>public.xml</string>
|
||||
</array>
|
||||
<key>UTTypeTagSpecification</key>
|
||||
<dict>
|
||||
<key>public.filename-extension</key>
|
||||
<array>
|
||||
<string>fb2</string>
|
||||
</array>
|
||||
<key>public.mime-type</key>
|
||||
<string>application/xml</string>
|
||||
</dict>
|
||||
</dict>
|
||||
|
||||
<dict>
|
||||
<key>UTTypeIdentifier</key>
|
||||
<string>com.readest.cbz</string>
|
||||
<key>UTTypeDescription</key>
|
||||
<string>CBZ Archive</string>
|
||||
<key>UTTypeConformsTo</key>
|
||||
<array>
|
||||
<string>public.archive</string>
|
||||
</array>
|
||||
<key>UTTypeTagSpecification</key>
|
||||
<dict>
|
||||
<key>public.filename-extension</key>
|
||||
<array>
|
||||
<string>cbz</string>
|
||||
</array>
|
||||
<key>public.mime-type</key>
|
||||
<string>application/x-cbz</string>
|
||||
</dict>
|
||||
</dict>
|
||||
|
||||
<dict>
|
||||
<key>UTTypeIdentifier</key>
|
||||
<string>org.mobipocket.mobi</string>
|
||||
<key>UTTypeDescription</key>
|
||||
<string>MOBI Document</string>
|
||||
<key>UTTypeConformsTo</key>
|
||||
<array>
|
||||
<string>public.data</string>
|
||||
</array>
|
||||
<key>UTTypeTagSpecification</key>
|
||||
<dict>
|
||||
<key>public.filename-extension</key>
|
||||
<array>
|
||||
<string>mobi</string>
|
||||
</array>
|
||||
<key>public.mime-type</key>
|
||||
<string>application/x-mobipocket-ebook</string>
|
||||
</dict>
|
||||
</dict>
|
||||
|
||||
<dict>
|
||||
<key>UTTypeIdentifier</key>
|
||||
<string>com.amazon.azw</string>
|
||||
<key>UTTypeDescription</key>
|
||||
<string>AZW Document</string>
|
||||
<key>UTTypeConformsTo</key>
|
||||
<array>
|
||||
<string>public.data</string>
|
||||
</array>
|
||||
<key>UTTypeTagSpecification</key>
|
||||
<dict>
|
||||
<key>public.filename-extension</key>
|
||||
<array>
|
||||
<string>azw</string>
|
||||
<string>azw3</string>
|
||||
</array>
|
||||
<key>public.mime-type</key>
|
||||
<string>application/vnd.amazon.ebook</string>
|
||||
</dict>
|
||||
</dict>
|
||||
|
||||
<dict>
|
||||
<key>UTTypeIdentifier</key>
|
||||
<string>public.plain-text</string>
|
||||
<key>UTTypeDescription</key>
|
||||
<string>Text File</string>
|
||||
<key>UTTypeConformsTo</key>
|
||||
<array>
|
||||
<string>public.data</string>
|
||||
<string>public.content</string>
|
||||
</array>
|
||||
<key>UTTypeTagSpecification</key>
|
||||
<dict>
|
||||
<key>public.filename-extension</key>
|
||||
<array>
|
||||
<string>txt</string>
|
||||
</array>
|
||||
<key>public.mime-type</key>
|
||||
<string>text/plain</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</array>
|
||||
|
||||
<key>UTExportedTypeDeclarations</key>
|
||||
<array>
|
||||
<dict>
|
||||
|
||||
+58
@@ -53,6 +53,11 @@ class SetScreenBrightnessRequestArgs: Decodable {
|
||||
let brightness: Float?
|
||||
}
|
||||
|
||||
class CopyUriToPathRequestArgs: Decodable {
|
||||
let uri: String?
|
||||
let dst: String?
|
||||
}
|
||||
|
||||
struct InitializeRequest: Decodable {
|
||||
let publicKey: String?
|
||||
}
|
||||
@@ -816,6 +821,59 @@ class NativeBridgePlugin: Plugin {
|
||||
}
|
||||
invoke.resolve(["success": true])
|
||||
}
|
||||
|
||||
@objc public func copy_uri_to_path(_ invoke: Invoke) {
|
||||
guard let args = try? invoke.parseArgs(CopyUriToPathRequestArgs.self) else {
|
||||
return invoke.reject("Failed to parse arguments")
|
||||
}
|
||||
|
||||
guard let uriString = args.uri, let dstPath = args.dst else {
|
||||
return invoke.reject("URI and destination path must be provided")
|
||||
}
|
||||
|
||||
guard let uri = URL(string: uriString) else {
|
||||
return invoke.reject("Invalid URI")
|
||||
}
|
||||
|
||||
let fileManager = FileManager.default
|
||||
let dstURL = URL(fileURLWithPath: dstPath)
|
||||
|
||||
do {
|
||||
let didStartAccessing = uri.startAccessingSecurityScopedResource()
|
||||
defer {
|
||||
if didStartAccessing {
|
||||
uri.stopAccessingSecurityScopedResource()
|
||||
}
|
||||
}
|
||||
|
||||
var shouldCopy = false
|
||||
|
||||
if fileManager.fileExists(atPath: dstURL.path) {
|
||||
let srcAttributes = try fileManager.attributesOfItem(atPath: uri.path)
|
||||
let dstAttributes = try fileManager.attributesOfItem(atPath: dstURL.path)
|
||||
|
||||
let srcModDate = srcAttributes[.modificationDate] as? Date ?? Date.distantPast
|
||||
let dstModDate = dstAttributes[.modificationDate] as? Date ?? Date.distantPast
|
||||
|
||||
if srcModDate > dstModDate {
|
||||
try fileManager.removeItem(at: dstURL)
|
||||
shouldCopy = true
|
||||
} else {
|
||||
shouldCopy = false
|
||||
}
|
||||
} else {
|
||||
shouldCopy = true
|
||||
}
|
||||
|
||||
if shouldCopy {
|
||||
try fileManager.copyItem(at: uri, to: dstURL)
|
||||
}
|
||||
|
||||
invoke.resolve(["success": true])
|
||||
} catch {
|
||||
invoke.reject("Failed to copy file: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@_cdecl("init_plugin_native_bridge")
|
||||
|
||||
@@ -72,6 +72,7 @@
|
||||
},
|
||||
"iOS": {
|
||||
"developmentTeam": "J5W48D69VR",
|
||||
"infoPlist": "./Info-ios.plist",
|
||||
"minimumSystemVersion": "14.0"
|
||||
},
|
||||
"fileAssociations": [
|
||||
|
||||
@@ -323,7 +323,7 @@ const LibraryPageContent = ({ searchParams }: { searchParams: ReadonlyURLSearchP
|
||||
};
|
||||
|
||||
const handleOpenWithBooks = async (appService: AppService, library: Book[]) => {
|
||||
const openWithFiles = (await parseOpenWithFiles()) || [];
|
||||
const openWithFiles = (await parseOpenWithFiles(appService)) || [];
|
||||
|
||||
if (openWithFiles.length > 0) {
|
||||
return await processOpenWithFiles(appService, openWithFiles, library);
|
||||
|
||||
@@ -178,7 +178,7 @@ const ReaderContent: React.FC<{ ids?: string; settings: SystemSettings }> = ({ i
|
||||
}
|
||||
dismissBook(bookKey);
|
||||
if (bookKeys.filter((key) => key !== bookKey).length == 0) {
|
||||
const openWithFiles = (await parseOpenWithFiles()) || [];
|
||||
const openWithFiles = (await parseOpenWithFiles(appService)) || [];
|
||||
if (appService?.hasWindow) {
|
||||
if (openWithFiles.length > 0) {
|
||||
tauriHandleOnCloseWindow(handleCloseBooks);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { isWebAppPlatform, hasCli } from '@/services/environment';
|
||||
import { AppService } from '@/types/system';
|
||||
import { getCurrent } from '@tauri-apps/plugin-deep-link';
|
||||
|
||||
declare global {
|
||||
@@ -35,14 +36,18 @@ const parseCLIOpenWithFiles = async () => {
|
||||
return files;
|
||||
};
|
||||
|
||||
const parseIntentOpenWithFiles = async () => {
|
||||
const parseIntentOpenWithFiles = async (appService: AppService | null) => {
|
||||
const urls = await getCurrent();
|
||||
if (urls && urls.length > 0) {
|
||||
console.log('Intent Open with URL:', urls);
|
||||
return urls
|
||||
.map((url) => {
|
||||
if (url.startsWith('file://')) {
|
||||
return decodeURI(url.replace('file://', ''));
|
||||
if (appService?.isIOSApp) {
|
||||
return decodeURI(url);
|
||||
} else {
|
||||
return decodeURI(url.replace('file://', ''));
|
||||
}
|
||||
} else if (url.startsWith('content://')) {
|
||||
return url;
|
||||
} else {
|
||||
@@ -55,7 +60,7 @@ const parseIntentOpenWithFiles = async () => {
|
||||
return null;
|
||||
};
|
||||
|
||||
export const parseOpenWithFiles = async () => {
|
||||
export const parseOpenWithFiles = async (appService: AppService | null) => {
|
||||
if (isWebAppPlatform()) return [];
|
||||
|
||||
let files = parseWindowOpenWithFiles();
|
||||
@@ -63,7 +68,7 @@ export const parseOpenWithFiles = async () => {
|
||||
files = await parseCLIOpenWithFiles();
|
||||
}
|
||||
if (!files || files.length === 0) {
|
||||
files = await parseIntentOpenWithFiles();
|
||||
files = await parseIntentOpenWithFiles(appService);
|
||||
}
|
||||
return files;
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
const DEFAULT_SHORTCUTS = {
|
||||
onSwitchSideBar: ['ctrl+Tab', 'opt+Tab', 'alt+Tab'],
|
||||
onToggleSideBar: ['s', 'F9'],
|
||||
onToggleSideBar: ['s'],
|
||||
onToggleNotebook: ['n'],
|
||||
onShowSearchBar: ['ctrl+f', 'cmd+f'],
|
||||
onToggleScrollMode: ['shift+j'],
|
||||
|
||||
@@ -36,7 +36,11 @@ export function useOpenWithBooks() {
|
||||
const filePaths = [];
|
||||
for (let url of urls) {
|
||||
if (url.startsWith('file://')) {
|
||||
url = decodeURI(url.replace('file://', ''));
|
||||
if (appService?.isIOSApp) {
|
||||
url = decodeURI(url);
|
||||
} else {
|
||||
url = decodeURI(url.replace('file://', ''));
|
||||
}
|
||||
}
|
||||
if (!/^(https?:|data:|blob:)/i.test(url)) {
|
||||
filePaths.push(url);
|
||||
@@ -73,20 +77,27 @@ export function useOpenWithBooks() {
|
||||
if (listenedOpenWithBooks.current) return;
|
||||
listenedOpenWithBooks.current = true;
|
||||
|
||||
const unlistenDeeplink = getCurrentWindow().listen('single-instance', ({ event, payload }) => {
|
||||
console.log('Received deep link:', event, payload);
|
||||
const { args } = payload as SingleInstancePayload;
|
||||
if (args?.[1]) {
|
||||
handleOpenWithFileUrl([args[1]]);
|
||||
}
|
||||
});
|
||||
// For Windows/Linux deep link and macOS open-file event
|
||||
const unlistenDeeplink = getCurrentWindow().listen<SingleInstancePayload>(
|
||||
'single-instance',
|
||||
({ event, payload }) => {
|
||||
console.log('Received deep link:', event, payload);
|
||||
const { args } = payload;
|
||||
if (args?.[1]) {
|
||||
handleOpenWithFileUrl([args[1]]);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// For Android "Share to Readest" intent
|
||||
let unlistenSharedIntent: Promise<PluginListener> | null = null;
|
||||
// FIXME: register/unregister plugin listeniner on iOS might cause app freeze for unknown reason
|
||||
// so we only register it on Android for now to support "Shared to Readest" feature
|
||||
if (appService?.isAndroidApp) {
|
||||
unlistenSharedIntent = initializeListeners();
|
||||
}
|
||||
|
||||
// iOS Open with URL event
|
||||
const listenOpenWithFiles = async () => {
|
||||
return await onOpenUrl((urls) => {
|
||||
handleOpenWithFileUrl(urls);
|
||||
|
||||
@@ -194,7 +194,7 @@ export const nativeFileSystem: FileSystem = {
|
||||
let fname = name || getFilename(fp);
|
||||
if (isValidURL(path)) {
|
||||
return await new RemoteFile(path, fname).open();
|
||||
} else if (isContentURI(path)) {
|
||||
} else if (isContentURI(path) || (isFileURI(path) && OS_TYPE === 'ios')) {
|
||||
fname = await basename(path);
|
||||
if (path.includes('com.android.externalstorage')) {
|
||||
// If the URI is from shared internal storage (like /storage/emulated/0),
|
||||
@@ -202,6 +202,7 @@ export const nativeFileSystem: FileSystem = {
|
||||
return await new NativeFile(fp, fname, baseDir ? baseDir : null).open();
|
||||
} else {
|
||||
// Otherwise, for content:// URIs (e.g. from MediaStore, Drive, or third-party apps),
|
||||
// or file:// URIs is security scoped resource in iOS (e.g. from Files app),
|
||||
// we cannot access the file directly — so we copy it to a temporary cache location.
|
||||
const prefix = await this.getPrefix('Cache');
|
||||
const dst = await join(prefix, fname);
|
||||
|
||||
Reference in New Issue
Block a user