diff --git a/src/i18n/localization/locales/en/translation.json b/src/i18n/localization/locales/en/translation.json index bd31aae..2646d5e 100644 --- a/src/i18n/localization/locales/en/translation.json +++ b/src/i18n/localization/locales/en/translation.json @@ -520,7 +520,9 @@ "TidGiMobileShare": "Shared from TidGi Mobile", "ImportSuccess": "Import Success", "TagForSharedContent": "Tag for shared content", - "Clipped": "Clipped" + "Clipped": "Clipped", + "FastImport": "Fast Import", + "FastImportDescription": "Import to default workspace without opening the wiki, but no popup box to confirm. You can manually open the wiki and view the imported results in “Recent”." }, "Description": "Description", "Tags": "Tags", diff --git a/src/i18n/localization/locales/zh_CN/translation.json b/src/i18n/localization/locales/zh_CN/translation.json index 8a784ae..6caddd9 100644 --- a/src/i18n/localization/locales/zh_CN/translation.json +++ b/src/i18n/localization/locales/zh_CN/translation.json @@ -506,7 +506,9 @@ "ImportSuccess": "导入成功", "TagForSharedContent": "分享内容标签", "TagForSharedContentDescription": "分享给太记移动端的内容,会自动加上这个标签。留空时表示不加任何标签。", - "Clipped": "剪藏" + "Clipped": "剪藏", + "FastImport": "快速导入", + "FastImportDescription": "无需打开Wiki即可导入内容到默认工作区,但不会有弹框确认。可以手动打开Wiki后到「最近」里查看导入的结果。" }, "Import": { "ImportWiki": "导入Wiki" diff --git a/src/pages/Config/Shared.tsx b/src/pages/Config/Shared.tsx index 2d3267b..9a30e43 100644 --- a/src/pages/Config/Shared.tsx +++ b/src/pages/Config/Shared.tsx @@ -3,7 +3,8 @@ import React, { useState } from 'react'; import { useTranslation } from 'react-i18next'; import { styled } from 'styled-components/native'; -import { TextInput, Text } from 'react-native-paper'; +import { Switch, Text, TextInput } from 'react-native-paper'; +import { FlexibleText, SwitchContainer } from '../../components/PreferenceWidgets'; import { useConfigStore } from '../../store/config'; const StyledTextInput = styled(TextInput)` @@ -13,7 +14,7 @@ const StyledTextInput = styled(TextInput)` export function Shared(): JSX.Element { const { t } = useTranslation(); - const [initialTagForSharedContent] = useConfigStore(state => [state.tagForSharedContent]); + const [initialTagForSharedContent, fastImport] = useConfigStore(state => [state.tagForSharedContent, state.fastImport]); const [tagForSharedContent, tagForSharedContentSetter] = useState(initialTagForSharedContent); const setConfig = useConfigStore(state => state.set); @@ -21,6 +22,10 @@ export function Shared(): JSX.Element { setConfig({ tagForSharedContent: newText }); }, []); + const fastImportOnChange = (value: boolean) => { + setConfig({ fastImport: value }); + }; + return ( <> {t('Share.TagForSharedContentDescription')} + {t('Share.FastImport')} + + {t('Share.FastImportDescription')} + + ); } diff --git a/src/services/NativeService/hooks.tsx b/src/services/NativeService/hooks.tsx index 1c8a8ec..24a5700 100644 --- a/src/services/NativeService/hooks.tsx +++ b/src/services/NativeService/hooks.tsx @@ -1,18 +1,11 @@ /* eslint-disable security-node/detect-crlf */ /* eslint-disable @typescript-eslint/strict-boolean-expressions */ -import { format } from 'date-fns'; -import * as fs from 'expo-file-system'; import type { useShareIntent as IUseShareIntent } from 'expo-share-intent'; -import { compact } from 'lodash'; -import { useEffect, useMemo, useState } from 'react'; +import { useEffect, useState } from 'react'; import { Snackbar } from 'react-native-paper'; import { useRegisterProxy } from 'react-native-postmessage-cat'; -import { ITiddlerFieldsParam } from 'tiddlywiki'; import i18n from '../../i18n'; -import { useConfigStore } from '../../store/config'; -import { IWikiWorkspace, useWorkspaceStore } from '../../store/workspace'; -import { WikiStorageService } from '../WikiStorageService'; import { nativeService } from '.'; import { NativeServiceIPCDescriptor } from './descriptor'; @@ -42,8 +35,6 @@ export function useRegisterReceivingShareIntent() { {i18n.t('Share.ImportSuccess')} ); - const [tagForSharedContent] = useConfigStore(state => [state.tagForSharedContent]); - const newTagForSharedContent = useMemo(() => tagForSharedContent ?? i18n.t('Share.Clipped'), [tagForSharedContent]); /** If you get error on development: * ``` @@ -75,44 +66,8 @@ export function useRegisterReceivingShareIntent() { void (async () => { try { if (!hasShareIntent) return; - const defaultWiki = compact(useWorkspaceStore.getState().workspaces).find((w): w is IWikiWorkspace => w.type === 'wiki'); - if (defaultWiki === undefined) return; await nativeService.receivingShareIntent(shareIntent); resetShareIntent(); - // put into default workspace's database, with random title - const storageOfDefaultWorkspace = new WikiStorageService(defaultWiki); - const randomTitle = `${i18n.t('Share.SharedContent')}-${Date.now()}`; - const created = format(new Date(), 'yyyyMMddHHmmssSSS'); - let fields: ITiddlerFieldsParam = { - created, - modified: created, - creator: i18n.t('Share.TidGiMobileShare'), - tags: newTagForSharedContent, - }; - if (shareIntent.webUrl) fields = { ...fields, url: shareIntent.webUrl }; - switch (shareIntent.type) { - case 'text': - case 'weburl': { - if (shareIntent.text) fields = { ...fields, text: shareIntent.text }; - await storageOfDefaultWorkspace.saveTiddler(shareIntent.meta?.title ?? randomTitle, fields); - break; - } - case 'media': - case 'file': { - if (shareIntent.files) { - for (const file of shareIntent.files) { - const fileContent = await fs.readAsStringAsync(file.path, { encoding: fs.EncodingType.Base64 }); - const fileFields = { - ...fields, - type: file.mimeType, - text: fileContent, - }; - await storageOfDefaultWorkspace.saveTiddler(file.fileName || randomTitle, fileFields); - } - } - break; - } - } setImportSuccessSnackBarVisible(true); } catch (error) { console.log( @@ -122,7 +77,7 @@ export function useRegisterReceivingShareIntent() { ); } })(); - }, [hasShareIntent, shareIntent, resetShareIntent, error, newTagForSharedContent]); + }, [hasShareIntent, shareIntent, resetShareIntent, error]); return { importSuccessSnackBar }; } diff --git a/src/services/NativeService/index.ts b/src/services/NativeService/index.ts index 91a6aca..ceff71e 100644 --- a/src/services/NativeService/index.ts +++ b/src/services/NativeService/index.ts @@ -1,11 +1,18 @@ /* eslint-disable @typescript-eslint/prefer-nullish-coalescing */ /* eslint-disable @typescript-eslint/strict-boolean-expressions */ +import { format } from 'date-fns'; import { Camera, PermissionStatus } from 'expo-camera'; import * as fs from 'expo-file-system'; -import { ShareIntent } from 'expo-share-intent'; +import type { ShareIntent } from 'expo-share-intent'; +import { compact } from 'lodash'; + +import type { ITiddlerFieldsParam } from 'tiddlywiki'; import { openDefaultWikiIfNotAlreadyThere } from '../../hooks/useAutoOpenDefaultWiki'; +import i18n from '../../i18n'; import { useConfigStore } from '../../store/config'; +import { IWikiWorkspace, useWorkspaceStore } from '../../store/workspace'; import type { WikiHookService } from '../WikiHookService'; +import { WikiStorageService } from '../WikiStorageService'; import { importBinaryTiddlers, importTextTiddlers } from './wikiOperations'; /** @@ -73,34 +80,40 @@ export class NativeService { } } + async receivingShareIntent(shareIntent: ShareIntent) { + const configs = useConfigStore.getState(); + if (configs.fastImport) { + await this.storeSharedContentToStorage(shareIntent); + } else { + await this.importSharedContentToWiki(shareIntent); + } + } + /** * If wiki has not started, android will store files in a queue, wait for getReceivedFiles to be called. * Even wiki previously loaded, but after go background for a while, it may be unloaded too. We need to wait not only webview loaded, need wiki core loaded, then call this. */ - async receivingShareIntent(shareIntent: ShareIntent) { - // wait for wiki start, and use injectJavascript to add tiddler, for user to edit. - // To get All Recived Urls - - // files returns as JSON Array example - // [{ filePath: null, text: null, weblink: null, mimeType: null, contentUri: null, fileName: null, extension: null }] + async importSharedContentToWiki(shareIntent: ShareIntent) { const { text, files } = shareIntent; let script = ''; - if (files !== null) { - console.log(text, files); - if (text) { - script = importTextTiddlers([text]); - } else { - if (files.length === 0) return; - const filesWithFileLoadedToText = await Promise.all(files.map(async (file) => { - if (file.path === null) return file; - /** - * based on tiddlywiki file type parsers `$tw.utils.registerFileType("image/jpeg","base64",[".jpg",".jpeg"],{flags:["image"]});` - * we need to use base64 encoding to load file - */ - const text = await fs.readAsStringAsync(file.path.startsWith('file://') ? file.path : `file://${file.path}`, { encoding: 'base64' }); - return { ...file, text }; - })); - script = importBinaryTiddlers(filesWithFileLoadedToText); + switch (shareIntent.type) { + case 'text': + case 'weburl': { + if (text) { + script = importTextTiddlers([text]); + } + break; + } + case 'media': + case 'file': { + if (files && files.length > 0) { + const filesWithFileLoadedToText = await Promise.all(files.map(async (file) => { + const text = await fs.readAsStringAsync(file.path.startsWith('file://') ? file.path : `file://${file.path}`, { encoding: 'base64' }); + return { ...file, text, type: file.mimeType }; + })); + script = importBinaryTiddlers(filesWithFileLoadedToText); + } + break; } } if (!script) return; @@ -109,6 +122,51 @@ export class NativeService { await wikiHookService.executeAfterTwReady(script); } + /** + * Directly store shared content to default workspace's sqlite, very fast, don't need to wait for wiki to load. + */ + async storeSharedContentToStorage(shareIntent: ShareIntent) { + const defaultWiki = compact(useWorkspaceStore.getState().workspaces).find((w): w is IWikiWorkspace => w.type === 'wiki'); + if (defaultWiki === undefined) return; + const storageOfDefaultWorkspace = new WikiStorageService(defaultWiki); + const configs = useConfigStore.getState(); + const tagForSharedContent = configs.tagForSharedContent; + const newTagForSharedContent = tagForSharedContent ?? i18n.t('Share.Clipped'); + // put into default workspace's database, with random title + const randomTitle = `${i18n.t('Share.SharedContent')}-${Date.now()}`; + const created = format(new Date(), 'yyyyMMddHHmmssSSS'); + let fields: ITiddlerFieldsParam = { + created, + modified: created, + creator: i18n.t('Share.TidGiMobileShare'), + tags: newTagForSharedContent, + }; + if (shareIntent.webUrl) fields = { ...fields, url: shareIntent.webUrl }; + switch (shareIntent.type) { + case 'text': + case 'weburl': { + if (shareIntent.text) fields = { ...fields, text: shareIntent.text }; + await storageOfDefaultWorkspace.saveTiddler(shareIntent.meta?.title ?? randomTitle, fields); + break; + } + case 'media': + case 'file': { + if (shareIntent.files) { + for (const file of shareIntent.files) { + const fileContent = await fs.readAsStringAsync(file.path.startsWith('file://') ? file.path : `file://${file.path}`, { encoding: fs.EncodingType.Base64 }); + const fileFields = { + ...fields, + type: file.mimeType, + text: fileContent, + }; + await storageOfDefaultWorkspace.saveTiddler(file.fileName || randomTitle, fileFields); + } + } + break; + } + } + } + async saveFileToFs(filename: string, text: string, mimeType?: string): Promise { const configs = useConfigStore.getState(); const result = await fs.StorageAccessFramework.requestDirectoryPermissionsAsync(configs.defaultDownloadLocation); diff --git a/src/store/config.ts b/src/store/config.ts index 6fa2af7..83fef3e 100644 --- a/src/store/config.ts +++ b/src/store/config.ts @@ -8,6 +8,7 @@ export interface ConfigState { /** the initial value should be undefined, so an initial true value won't immediately trigger autoOpen */ autoOpenDefaultWiki?: boolean; defaultDownloadLocation?: string; + fastImport: boolean; hideStatusBar?: boolean; keepAliveInBackground: boolean; preferredLanguage?: string; @@ -23,6 +24,7 @@ export interface ConfigState { } const defaultConfig: ConfigState = { autoOpenDefaultWiki: undefined, + fastImport: true, hideStatusBar: false, keepAliveInBackground: true, preferredLanguage: undefined, @@ -30,9 +32,9 @@ const defaultConfig: ConfigState = { syncInBackground: true, syncInterval: 60 * 1000, syncIntervalBackground: 60 * 30 * 1000, + tagForSharedContent: undefined, theme: 'default', translucentStatusBar: true, - tagForSharedContent: undefined, userName: '', }; interface ConfigActions {