@bardway/trpg-notebook-native 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/NOTICE +10 -0
- package/README.md +33 -2
- package/android/build.gradle +22 -0
- package/android/src/main/AndroidManifest.xml +1 -0
- package/android/src/main/java/expo/modules/notebookstorage/NotebookStorageModule.kt +51 -0
- package/android/src/main/java/expo/modules/notebookstorage/NotebookStore.kt +650 -0
- package/app.plugin.cjs +69 -0
- package/dist/AppMarkdown.d.ts +10 -0
- package/dist/AppMarkdownSections.d.ts +23 -0
- package/dist/NotebookPage.d.ts +8 -0
- package/dist/NotebookReaderFrame.d.ts +14 -0
- package/dist/chunked-secure.d.ts +19 -0
- package/dist/chunked-secure.js +180 -0
- package/dist/chunked-secure.js.map +7 -0
- package/dist/importPicker.d.ts +7 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +1094 -0
- package/dist/index.js.map +7 -0
- package/dist/reader/MarkdownDocumentView.d.ts +16 -0
- package/dist/reader/MarkdownView.d.ts +15 -0
- package/dist/reader/ReaderHost.d.ts +15 -0
- package/dist/reader/ReaderNativeConfig.d.ts +4 -0
- package/dist/storage-drivers.d.ts +28 -0
- package/dist/storage-drivers.js +35 -0
- package/dist/storage-drivers.js.map +7 -0
- package/dist/storage.d.ts +3 -0
- package/expo-module.config.json +4 -0
- package/native/TrpgMarkdownWebViewManager.kt +149 -0
- package/package.json +76 -4
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/storage.ts", "../src/importPicker.ts", "../src/reader/ReaderHost.tsx", "../src/reader/ReaderNativeConfig.ts", "../src/reader/MarkdownView.tsx", "../src/reader/MarkdownDocumentView.tsx", "../src/NotebookReaderFrame.tsx", "../src/NotebookPage.tsx", "../src/AppMarkdown.tsx", "../src/AppMarkdownSections.tsx"],
|
|
4
|
+
"sourcesContent": ["import type { NotebookStorageAdapter } from '@bardway/trpg-notebook';\n\ninterface NativeNotebookStorage {\n capabilities(): Promise<string>;\n ensureWorkspace(payload: string): Promise<string>;\n readDocument(payload: string): Promise<string>;\n initializeMemoTemplate(payload: string): Promise<string>;\n importDocument(payload: string): Promise<string>;\n commitOperation(payload: string): Promise<string>;\n saveViewport(payload: string): Promise<string>;\n saveLayoutProfile(payload: string): Promise<string>;\n setActiveDocument(payload: string): Promise<string>;\n deleteSession(payload: string): Promise<string>;\n readImport(payload: string): Promise<string>;\n flush(): Promise<string>;\n}\n\nlet nativePromise: Promise<NativeNotebookStorage> | null = null;\n\nconst native = (): Promise<NativeNotebookStorage> => {\n if (!nativePromise) {\n // Lazy loading keeps session bootstrap independent of an unopened notebook.\n // There is deliberately no memory fallback for authoritative notebook data.\n nativePromise = import('expo')\n .then(({ requireNativeModule }) =>\n requireNativeModule<NativeNotebookStorage>('NotebookStorage'),\n )\n .catch(() => {\n nativePromise = null;\n throw new Error('\u624B\u8BB0\u672C\u5730\u5B58\u50A8\u4E0D\u53EF\u7528\uFF0C\u8BF7\u91CD\u65B0\u7F16\u8BD1 Android \u5E94\u7528');\n });\n }\n return nativePromise;\n};\n\nconst decode = (payload: string): unknown => JSON.parse(payload);\n\nexport const notebookNativeStorage: NotebookStorageAdapter = {\n capabilities: async () => decode(await (await native()).capabilities()),\n ensureWorkspace: async (accountId, sessionId) =>\n decode(await (await native()).ensureWorkspace(JSON.stringify({ accountId, sessionId }))),\n readDocument: async (context, documentId) =>\n decode(await (await native()).readDocument(JSON.stringify({ context, documentId }))),\n initializeMemoTemplate: async (context, documentId, template) =>\n decode(\n await (\n await native()\n ).initializeMemoTemplate(JSON.stringify({ context, documentId, template })),\n ),\n importDocument: async (context, document) =>\n decode(await (await native()).importDocument(JSON.stringify({ context, document }))),\n commitOperation: async (context, documentId, operation) =>\n decode(\n await (await native()).commitOperation(JSON.stringify({ context, documentId, operation })),\n ),\n saveViewport: async (context, documentId, viewport, memoTemplateId) => {\n await (\n await native()\n ).saveViewport(\n JSON.stringify({\n context,\n documentId,\n viewport,\n ...(memoTemplateId ? { memoTemplateId } : {}),\n }),\n );\n },\n saveLayoutProfile: async (context, documentId, layout, viewport, memoTemplateId) => {\n await (\n await native()\n ).saveLayoutProfile(\n JSON.stringify({\n context,\n documentId,\n layout,\n viewport,\n ...(memoTemplateId ? { memoTemplateId } : {}),\n }),\n );\n },\n setActiveDocument: async (context, documentId) => {\n await (await native()).setActiveDocument(JSON.stringify({ context, documentId }));\n },\n deleteSession: async (accountId, sessionId) => {\n await (await native()).deleteSession(JSON.stringify({ accountId, sessionId }));\n },\n flush: async () => {\n // An unused notebook must not make logout depend on loading a native module.\n if (nativePromise) await (await nativePromise).flush();\n },\n};\n\nexport const readNotebookMarkdownImport = async (uri: string): Promise<unknown> =>\n decode(await (await native()).readImport(JSON.stringify({ uri })));\n", "import * as DocumentPicker from 'expo-document-picker';\nimport { File, Paths } from 'expo-file-system';\n\nexport interface NotebookPickedFile {\n uri: string;\n name: string;\n size?: number;\n}\n\nexport const pickNotebookMarkdownFiles = async (): Promise<NotebookPickedFile[] | null> => {\n const result = await DocumentPicker.getDocumentAsync({\n type: '*/*',\n multiple: true,\n copyToCacheDirectory: true,\n });\n if (result.canceled) return null;\n return result.assets.map(({ uri, name, size }) => ({ uri, name, size }));\n};\n\nexport const cleanupNotebookImportCopy = (uri: string): void => {\n // Only DocumentPicker's application-owned cache is removable. Never touch a provider's source.\n const cache = Paths.cache.uri.endsWith('/') ? Paths.cache.uri : `${Paths.cache.uri}/`;\n if (!uri.startsWith(cache) || uri.includes('/../') || uri.includes('%2e')) return;\n const file = new File(uri);\n if (file.exists) file.delete();\n};\n", "import React, { useCallback, useEffect, useId, useMemo, useRef, useState } from 'react';\nimport {\n Linking,\n NativeModules,\n Platform,\n Text,\n UIManager,\n View,\n findNodeHandle,\n} from 'react-native';\nimport WebView, { type WebViewMessageEvent } from 'react-native-webview';\nimport { useTheme } from '@bardway/trpg-ui-native';\nimport { resolveTypographyTextStyle } from '@bardway/trpg-ui-native';\nconst describeSafeFailure = (value: unknown) => ({\n errorType: value instanceof Error ? value.name : 'UnknownFailure',\n});\n\nimport { ThemedButton } from '@bardway/trpg-ui-native';\nimport { safeExternalLink } from '@bardway/trpg-markdown/protocol';\nimport { createReaderChunks, ReaderChunkAssembler } from '@bardway/trpg-markdown/protocol';\nimport {\n READER_ORIGIN,\n READER_PROTOCOL_VERSION,\n READER_URL,\n type ReaderEnvelope,\n} from '@bardway/trpg-markdown/protocol';\nimport { READER_NATIVE_CONFIG } from './ReaderNativeConfig.js';\n\nconst SOURCE = { uri: READER_URL };\ntype ReaderFailureStage =\n | 'initialization'\n | 'document-transfer'\n | 'renderer'\n | 'external-link'\n | 'bridge-message'\n | 'resource-load';\nexport const isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value);\n\nexport interface ReaderHostHandle {\n send(type: string, payload?: unknown): void;\n}\ninterface ReaderHostProps {\n documentKey: string;\n payload: unknown;\n active?: boolean;\n viewport?: boolean;\n onEvent(type: string, payload: unknown, handle: ReaderHostHandle): void;\n onError?(message: string): void;\n}\n\nexport const ReaderHost = React.memo(function ReaderHost({\n documentKey,\n payload,\n active = true,\n viewport = false,\n onEvent,\n onError,\n}: ReaderHostProps) {\n const { theme, typography, logger } = useTheme();\n const reactId = useId();\n const instanceId = useMemo(() => `reader-${reactId}-${Date.now().toString(36)}`, [reactId]);\n const nonce = useRef(`${Math.random().toString(36)}${Math.random().toString(36)}`).current;\n const webview = useRef<WebView>(null);\n const container = useRef<View>(null);\n const [ready, setReady] = useState(false);\n const [height, setHeight] = useState(1);\n const [failure, setFailure] = useState<string | null>(null);\n const currentReady = useRef(ready);\n currentReady.current = ready;\n const errorText = resolveTypographyTextStyle(typography, {\n role: 'body',\n fontSize: 13,\n metric: 'body',\n });\n const eventRoutes = useRef(new Map<string, ReaderHostProps['onEvent']>());\n eventRoutes.current.set(documentKey, onEvent);\n if (eventRoutes.current.size > 24) {\n const first = eventRoutes.current.keys().next().value;\n if (first) eventRoutes.current.delete(first);\n }\n const currentKey = useRef(documentKey);\n currentKey.current = documentKey;\n const currentActive = useRef(active);\n currentActive.current = active;\n const errorCallback = useRef(onError);\n errorCallback.current = onError;\n const incoming = useRef(new ReaderChunkAssembler());\n const sendForKey = useCallback(\n (key: string, type: string, data?: unknown) => {\n webview.current?.postMessage(\n JSON.stringify({\n protocol: READER_PROTOCOL_VERSION,\n instanceId,\n nonce,\n documentKey: key,\n type,\n payload: data,\n }),\n );\n },\n [instanceId, nonce],\n );\n const report = useCallback(\n (message: string, stage: ReaderFailureStage, cause?: unknown) => {\n setFailure(message);\n errorCallback.current?.(message);\n logger.warn('[MarkdownReader] Reader failed', {\n instanceId,\n stage,\n ...describeSafeFailure(message),\n ...(cause === undefined ? {} : { cause: describeSafeFailure(cause) }),\n ready: currentReady.current,\n active: currentActive.current,\n });\n },\n [instanceId, logger],\n );\n const serialized = useMemo(() => JSON.stringify(payload), [payload]);\n useEffect(() => {\n if (ready) return;\n const timeout = setTimeout(\n () =>\n report('\u672C\u5730\u9605\u8BFB\u5668\u672A\u5C31\u7EEA\uFF0C\u8BF7\u68C0\u67E5 Android System WebView \u7248\u672C\u5E76\u91CD\u65B0\u6253\u5F00', 'initialization'),\n 15000,\n );\n return () => clearTimeout(timeout);\n }, [ready, report]);\n\n useEffect(() => {\n if (!ready) return;\n setFailure(null);\n const id = `${documentKey}-${Date.now()}-${Math.random()}`;\n try {\n for (const chunk of createReaderChunks(serialized, id))\n sendForKey(documentKey, 'documentChunk', chunk);\n } catch (cause: unknown) {\n report('\u6587\u6863\u8D85\u8FC7\u9605\u8BFB\u5668\u6D88\u606F\u5BB9\u91CF\uFF0C\u539F\u59CB\u6570\u636E\u5DF2\u4FDD\u7559', 'document-transfer', cause);\n }\n sendForKey(documentKey, 'setActive', currentActive.current);\n }, [ready, documentKey, serialized, sendForKey, report]);\n useEffect(() => {\n if (ready) sendForKey(documentKey, 'setActive', active);\n }, [ready, active, documentKey, sendForKey]);\n\n const processMessage = useCallback(\n function process(message: ReaderEnvelope) {\n if (message.type === 'readerReady') {\n setReady(true);\n return;\n }\n if (message.type === 'bridgeChunk') {\n const combined = incoming.current.accept(message.documentKey, message.payload);\n if (combined !== null) {\n const value: unknown = JSON.parse(combined);\n if (isRecord(value) && typeof value.type === 'string' && value.type !== 'bridgeChunk')\n process({ ...message, type: value.type, payload: value.payload });\n }\n return;\n }\n if (message.type === 'readerError') {\n if (message.documentKey !== currentKey.current) return;\n report(\n isRecord(message.payload) && typeof message.payload.message === 'string'\n ? message.payload.message\n : '\u9605\u8BFB\u5668\u52A0\u8F7D\u5931\u8D25',\n 'renderer',\n );\n return;\n }\n if (message.type === 'openExternalLink') {\n const url =\n isRecord(message.payload) && typeof message.payload.url === 'string'\n ? safeExternalLink(message.payload.url)\n : null;\n if (url && message.documentKey === currentKey.current)\n void Linking.openURL(url).catch((cause: unknown) =>\n report('\u94FE\u63A5\u65E0\u6CD5\u6253\u5F00', 'external-link', cause),\n );\n return;\n }\n if (\n message.type === 'anchorRequested' &&\n message.documentKey === currentKey.current &&\n isRecord(message.payload)\n ) {\n const y = message.payload.y;\n const bridge: unknown = NativeModules.TrpgMarkdownReaderBridge;\n const tag = findNodeHandle(container.current);\n if (\n tag !== null &&\n typeof y === 'number' &&\n Number.isFinite(y) &&\n y >= 0 &&\n y <= 1_000_000 &&\n isRecord(bridge) &&\n typeof bridge.revealAnchor === 'function'\n )\n bridge.revealAnchor(tag, y);\n return;\n }\n if (\n message.type === 'blockMetrics' &&\n message.documentKey === currentKey.current &&\n isRecord(message.payload)\n ) {\n const next = message.payload.height;\n if (typeof next === 'number' && Number.isFinite(next) && next > 0 && next <= 1_000_000)\n setHeight((old) => (Math.abs(old - next) < 1 ? old : Math.ceil(next)));\n }\n eventRoutes.current.get(message.documentKey)?.(message.type, message.payload, {\n send: (type, data) => sendForKey(message.documentKey, type, data),\n });\n },\n [report, sendForKey],\n );\n\n const onMessage = useCallback(\n (event: WebViewMessageEvent) => {\n if (\n !event.nativeEvent.url.startsWith(`${READER_ORIGIN}/reader/`) ||\n event.nativeEvent.data.length > 100_000\n )\n return;\n try {\n const value: unknown = JSON.parse(event.nativeEvent.data);\n if (\n !isRecord(value) ||\n value.protocol !== READER_PROTOCOL_VERSION ||\n value.instanceId !== instanceId ||\n value.nonce !== nonce ||\n typeof value.documentKey !== 'string' ||\n value.documentKey.length > 512 ||\n typeof value.type !== 'string'\n )\n return;\n processMessage({\n protocol: READER_PROTOCOL_VERSION,\n instanceId,\n nonce,\n documentKey: value.documentKey,\n type: value.type,\n payload: value.payload,\n });\n } catch (cause: unknown) {\n report('\u9605\u8BFB\u5668\u6D88\u606F\u65E0\u6548', 'bridge-message', cause);\n }\n },\n [instanceId, nonce, processMessage, report],\n );\n\n if (Platform.OS !== 'android' || !UIManager.getViewManagerConfig('TrpgMarkdownWebView')) {\n return (\n <Text\n style={{\n color: theme.colors.text.muted,\n fontFamily: typography.body,\n fontSize: 14,\n lineHeight: 21,\n includeFontPadding: false,\n }}\n >\n \u5F53\u524D\u5B89\u88C5\u5305\u7F3A\u5C11\u672C\u5730\u9605\u8BFB\u5668\uFF0C\u8BF7\u91CD\u65B0\u7F16\u8BD1 Android APK\u3002\n </Text>\n );\n }\n return (\n <View\n ref={container}\n collapsable={false}\n style={viewport ? { flex: 1, minHeight: 1 } : { height: Math.max(1, height) }}\n >\n <WebView\n ref={webview}\n nativeConfig={READER_NATIVE_CONFIG}\n source={SOURCE}\n onMessage={onMessage}\n style={{ backgroundColor: 'transparent' }}\n containerStyle={{ backgroundColor: 'transparent' }}\n onLoadEnd={() => {\n setReady(false);\n sendForKey(currentKey.current, 'initialize');\n }}\n onError={(event) =>\n report('\u9605\u8BFB\u5668\u8D44\u6E90\u52A0\u8F7D\u5931\u8D25\uFF0C\u53EF\u91CD\u65B0\u6253\u5F00\u6587\u6863', 'resource-load', {\n code: event.nativeEvent.code,\n })\n }\n onRenderProcessGone={() => {\n setReady(false);\n webview.current?.reload();\n }}\n onShouldStartLoadWithRequest={(request) => request.url === READER_URL}\n javaScriptEnabled\n domStorageEnabled={false}\n allowFileAccess={false}\n allowFileAccessFromFileURLs={false}\n allowUniversalAccessFromFileURLs={false}\n mixedContentMode=\"never\"\n thirdPartyCookiesEnabled={false}\n sharedCookiesEnabled={false}\n setSupportMultipleWindows={false}\n setBuiltInZoomControls={false}\n scalesPageToFit={false}\n scrollEnabled={viewport}\n textZoom={100}\n androidLayerType=\"hardware\"\n />\n {failure ? (\n <View\n accessibilityLiveRegion=\"assertive\"\n style={{\n position: 'absolute',\n bottom: 0,\n left: 0,\n right: 0,\n backgroundColor: theme.colors.surface.actionDanger,\n borderTopWidth: 1,\n borderColor: theme.colors.status.error,\n padding: theme.layout.contentPadding.compact,\n flexDirection: 'row',\n alignItems: 'center',\n gap: theme.layout.density.spacing.groupY,\n }}\n >\n <Text\n accessibilityRole=\"alert\"\n style={[errorText, { color: theme.colors.text.primary, flex: 1 }]}\n >\n {failure}\n </Text>\n <ThemedButton\n label=\"\u91CD\u8BD5\"\n accessibilityLabel=\"\u91CD\u65B0\u52A0\u8F7D\u9605\u8BFB\u5668\"\n variant=\"danger\"\n style={{\n minHeight: theme.layout.touch.minimumTarget,\n minWidth: theme.layout.touch.minimumTarget,\n }}\n onPress={() => {\n incoming.current = new ReaderChunkAssembler();\n setFailure(null);\n setReady(false);\n webview.current?.reload();\n }}\n />\n </View>\n ) : null}\n </View>\n );\n});\n", "import { Platform, requireNativeComponent } from 'react-native';\nimport type { NativeProps } from 'react-native-webview/src/RNCWebViewNativeComponent';\n\n// Keep native view registration outside the host component's Fast Refresh boundary.\nconst ReaderNativeComponent =\n Platform.OS === 'android'\n ? requireNativeComponent<NativeProps>('TrpgMarkdownWebView')\n : undefined;\n\nexport const READER_NATIVE_CONFIG = ReaderNativeComponent\n ? { component: ReaderNativeComponent }\n : undefined;\n", "import React, { useCallback, useMemo } from 'react';\nimport { useTheme } from '@bardway/trpg-ui-native';\nimport type {\n MarkdownBlock,\n MarkdownBlockMetric,\n MarkdownScrollMode,\n MarkdownSurface,\n MarkdownTheme,\n MarkdownVariant,\n} from '@bardway/trpg-markdown/protocol';\nimport { isRecord, ReaderHost } from './ReaderHost.js';\n\nexport interface MarkdownViewProps {\n documentKey: string;\n markdown?: string;\n blocks?: readonly MarkdownBlock[];\n variant?: MarkdownVariant;\n surface?: MarkdownSurface;\n scrollMode?: MarkdownScrollMode;\n columns?: number;\n onBlockMetrics?(metrics: readonly MarkdownBlockMetric[]): void;\n}\n\nexport const useMarkdownTheme = (surface: MarkdownSurface): MarkdownTheme => {\n const { theme, typography } = useTheme();\n return useMemo(\n () => ({\n body: surface === 'paper' ? theme.colors.notebook.text : theme.colors.text.markdownBody,\n heading: surface === 'paper' ? theme.colors.notebook.text : theme.colors.text.accent,\n muted: surface === 'paper' ? theme.colors.notebook.muted : theme.colors.text.muted,\n link: surface === 'paper' ? theme.colors.notebook.link : theme.colors.text.accent,\n border: surface === 'paper' ? theme.colors.notebook.border : theme.colors.border.default,\n codeBackground:\n surface === 'paper' ? theme.colors.notebook.codeBackground : theme.colors.surface.card,\n background: surface === 'paper' ? theme.colors.notebook.paper : 'transparent',\n paperShade: surface === 'paper' ? theme.colors.notebook.paperShade : undefined,\n paperFiber: surface === 'paper' ? theme.colors.notebook.paperFiber : undefined,\n sizeScale: surface === 'paper' ? 1 : typography.sizeScale,\n narrativeFamily: typography.narrative,\n titleFamily: typography.title,\n uiFamily: typography.ui,\n warning: theme.colors.functional.warning,\n success: theme.colors.functional.success,\n }),\n [surface, theme.colors, typography],\n );\n};\n\nexport const MarkdownView = React.memo(function MarkdownView({\n documentKey,\n markdown,\n blocks,\n variant = 'body',\n surface = 'app',\n scrollMode = 'intrinsic',\n columns = 1,\n onBlockMetrics,\n}: MarkdownViewProps) {\n const theme = useMarkdownTheme(surface);\n const payload = useMemo(\n () => ({\n blocks: blocks ?? [{ id: 'body', markdown: markdown ?? '', variant }],\n theme,\n scrollMode,\n columns,\n }),\n [blocks, markdown, variant, theme, scrollMode, columns],\n );\n const onEvent = useCallback(\n (type: string, data: unknown) => {\n if (type !== 'blockMetrics' || !isRecord(data) || !Array.isArray(data.metrics)) return;\n const metrics: MarkdownBlockMetric[] = [];\n for (const entry of data.metrics) {\n if (\n !isRecord(entry) ||\n typeof entry.id !== 'string' ||\n typeof entry.top !== 'number' ||\n typeof entry.height !== 'number' ||\n !Number.isFinite(entry.top) ||\n !Number.isFinite(entry.height) ||\n entry.height < 0\n )\n return;\n metrics.push({\n id: entry.id,\n top: entry.top,\n height: entry.height,\n left: typeof entry.left === 'number' ? entry.left : 0,\n width: typeof entry.width === 'number' ? entry.width : undefined,\n });\n }\n onBlockMetrics?.(metrics);\n },\n [onBlockMetrics],\n );\n return (\n <ReaderHost\n documentKey={documentKey}\n payload={payload}\n viewport={scrollMode === 'viewport'}\n onEvent={onEvent}\n />\n );\n});\nexport const MarkdownBlocksView = MarkdownView;\n", "import React, { useCallback, useEffect, useMemo, useRef } from 'react';\nimport { useTheme } from '@bardway/trpg-ui-native';\nimport { resolveNotebookPaperTypography } from '@bardway/trpg-notebook';\nimport { NOTEBOOK_LIMITS } from '@bardway/trpg-notebook';\nimport type {\n NotebookContext,\n NotebookDocument,\n NotebookLayoutProfile,\n NotebookOperation,\n NotebookSaveAcknowledgement,\n NotebookViewport,\n} from '@bardway/trpg-notebook';\nimport { isRecord, ReaderHost, type ReaderHostHandle } from './ReaderHost.js';\nimport { useMarkdownTheme } from './MarkdownView.js';\n\nexport interface MarkdownDocumentViewProps {\n context: NotebookContext;\n document: NotebookDocument;\n isActive: boolean;\n tool: 'pen' | 'eraser';\n retryToken?: number;\n onOperation(operation: NotebookOperation): Promise<NotebookSaveAcknowledgement>;\n onViewport(viewport: NotebookViewport): Promise<void> | void;\n onLayoutProfile(profile: NotebookLayoutProfile, viewport?: NotebookViewport): Promise<void>;\n onError(message: string): void;\n onReady?(): void;\n onSaveStateChange?(state: 'saved' | 'saving' | 'failed'): void;\n}\nconst finite = (value: unknown): value is number =>\n typeof value === 'number' && Number.isFinite(value);\nconst viewportGuard = (value: unknown): value is NotebookViewport =>\n isRecord(value) &&\n finite(value.scale) &&\n value.scale >= NOTEBOOK_LIMITS.minScale &&\n value.scale <= NOTEBOOK_LIMITS.maxScale &&\n finite(value.paperOffsetX) &&\n Math.abs(value.paperOffsetX) <= 1e7 &&\n finite(value.paperOffsetY) &&\n Math.abs(value.paperOffsetY) <= 1e7;\nconst operationGuard = (value: unknown): value is NotebookOperation => {\n if (!isRecord(value) || typeof value.opId !== 'string' || value.opId.length > 160) return false;\n if (value.type === 'eraseStrokes')\n return (\n Array.isArray(value.strokeIds) &&\n value.strokeIds.length <= 100_000 &&\n value.strokeIds.every((id) => typeof id === 'string')\n );\n if (value.type !== 'addStroke' || !isRecord(value.stroke)) return false;\n const stroke = value.stroke;\n return (\n typeof stroke.id === 'string' &&\n typeof stroke.documentId === 'string' &&\n finite(stroke.width) &&\n stroke.width > 0 &&\n finite(stroke.layoutVersion) &&\n typeof stroke.color === 'string' &&\n typeof stroke.createdAt === 'string' &&\n (stroke.coordinateSpace === undefined || stroke.coordinateSpace === 'paper') &&\n Array.isArray(stroke.points) &&\n stroke.points.length > 0 &&\n stroke.points.length <= 100_000 &&\n stroke.points.every(\n (point) =>\n isRecord(point) &&\n finite(point.x) &&\n finite(point.y) &&\n (point.pressure === undefined || finite(point.pressure)),\n )\n );\n};\nconst layoutGuard = (value: unknown): value is NotebookLayoutProfile =>\n isRecord(value) &&\n ['rendererVersion', 'fontAssetVersion', 'styleProfileId', 'fingerprint'].every(\n (key) => typeof value[key] === 'string',\n ) &&\n ['layoutVersion', 'paperWidth', 'paperHeight', 'contentWidth'].every(\n (key) => finite(value[key]) && Number(value[key]) > 0,\n ) &&\n (value.memoTemplateId === undefined || typeof value.memoTemplateId === 'string');\n\nexport const MarkdownDocumentView = React.memo(function MarkdownDocumentView(\n props: MarkdownDocumentViewProps,\n) {\n const { theme } = useTheme();\n const markdownTheme = useMarkdownTheme('paper');\n const handle = useRef<ReaderHostHandle | null>(null);\n const templateId = props.document.memoTemplate?.id;\n const key = `${props.context.accountId}/${props.context.sessionId}/${props.context.generation}/${props.document.id}${templateId ? `/template/${templateId}` : ''}`;\n const activeKey = useRef(key);\n activeKey.current = key;\n const activeDocumentId = useRef(props.document.id);\n activeDocumentId.current = props.document.id;\n const payload = useMemo(() => {\n const paperTypography = resolveNotebookPaperTypography(props.document);\n return {\n document: props.document,\n context: props.context,\n theme: { ...markdownTheme, sizeScale: paperTypography.sizeScale },\n profile: {\n rendererVersion: '1',\n fontAssetVersion: '1',\n styleProfileId: paperTypography.styleProfileId,\n paperWidth: 1600,\n inkColor: theme.colors.notebook.ink,\n inkWidth: 3,\n },\n };\n }, [props.document, props.context, markdownTheme, theme.colors.notebook.ink]);\n useEffect(() => {\n handle.current?.send('setTool', props.tool);\n }, [props.tool]);\n useEffect(() => {\n if (props.retryToken) handle.current?.send('retrySaves');\n }, [props.retryToken]);\n const onEvent = useCallback(\n (type: string, payloadValue: unknown, bridge: ReaderHostHandle) => {\n if (key === activeKey.current) handle.current = bridge;\n if (!isRecord(payloadValue)) return;\n // A delayed event from the pre-template page must never overwrite the initialized memo.\n if (\n (key !== activeKey.current && props.document.id === activeDocumentId.current) ||\n payloadValue.memoTemplateId !== templateId\n )\n return;\n if (type === 'layoutReady' && layoutGuard(payloadValue.layout)) {\n if (key !== activeKey.current || payloadValue.layout.memoTemplateId !== templateId) return;\n if (payloadValue.viewport !== undefined && !viewportGuard(payloadValue.viewport)) {\n bridge.send('layoutAccepted', false);\n props.onError('\u7EB8\u9762\u9605\u8BFB\u4F4D\u7F6E\u65E0\u6548\uFF0C\u5DF2\u4FDD\u7559\u539F\u59CB\u6570\u636E');\n return;\n }\n void props\n .onLayoutProfile(payloadValue.layout, payloadValue.viewport)\n .then(() => {\n if (key !== activeKey.current) return;\n bridge.send('layoutAccepted', true);\n bridge.send('setTool', props.tool);\n props.onReady?.();\n })\n .catch((error) => {\n if (key !== activeKey.current) return;\n bridge.send('layoutAccepted', false);\n props.onError(error instanceof Error ? error.message : '\u7EB8\u9762\u7248\u672C\u4E0D\u517C\u5BB9\uFF0C\u7B14\u8FF9\u5DF2\u4FDD\u7559');\n });\n } else if (\n (type === 'strokeCommitted' || type === 'strokesErased') &&\n operationGuard(payloadValue.operation)\n ) {\n const operation = payloadValue.operation;\n void props\n .onOperation(operation)\n .then((ack) => bridge.send('saveAcknowledged', ack))\n .catch((error) => {\n const message = error instanceof Error ? error.message : '\u4FDD\u5B58\u5931\u8D25\uFF0C\u53EF\u91CD\u8BD5';\n bridge.send('saveFailed', { opId: operation.opId, message });\n props.onError(message);\n });\n } else if (type === 'viewportChanged' && isRecord(payloadValue.viewport)) {\n const { scale, paperOffsetX, paperOffsetY } = payloadValue.viewport;\n if (\n finite(scale) &&\n finite(paperOffsetX) &&\n finite(paperOffsetY) &&\n scale >= 0.1 &&\n scale <= 8\n )\n void Promise.resolve(props.onViewport({ scale, paperOffsetX, paperOffsetY })).catch(() =>\n props.onError('\u9605\u8BFB\u4F4D\u7F6E\u4FDD\u5B58\u5931\u8D25'),\n );\n } else if (type === 'saveStateChanged') {\n const state = payloadValue.state;\n if (state === 'saved' || state === 'saving' || state === 'failed')\n props.onSaveStateChange?.(state);\n }\n },\n [props, key, templateId],\n );\n return (\n <ReaderHost\n documentKey={key}\n payload={payload}\n active={props.isActive}\n viewport\n onEvent={onEvent}\n onError={props.onError}\n />\n );\n});\n", "import React, { useRef } from 'react';\nimport { ActivityIndicator, StyleSheet, View } from 'react-native';\nimport {\n MarkdownDocumentView,\n type MarkdownDocumentViewProps,\n} from './reader/MarkdownDocumentView.js';\n\ninterface NotebookReaderFrameProps {\n scopeKey: string;\n readerProps: MarkdownDocumentViewProps | null;\n loading: boolean;\n active: boolean;\n loadingColor: string;\n loadingBackground: string;\n children?: React.ReactNode;\n}\n\n/** A session keeps its one reader even while the next document is being read. */\nexport function NotebookReaderFrame({\n scopeKey,\n readerProps,\n loading,\n active,\n loadingColor,\n loadingBackground,\n children,\n}: NotebookReaderFrameProps) {\n const retained = useRef<{ scopeKey: string; props: MarkdownDocumentViewProps } | null>(null);\n if (retained.current?.scopeKey !== scopeKey) retained.current = null;\n if (readerProps) retained.current = { scopeKey, props: readerProps };\n const ready = retained.current?.props;\n return (\n <View style={styles.root}>\n {ready ? (\n <MarkdownDocumentView\n key={scopeKey}\n {...ready}\n isActive={active && !loading && readerProps !== null}\n />\n ) : !loading ? (\n children\n ) : null}\n {loading ? (\n <View\n pointerEvents=\"auto\"\n accessibilityLabel=\"\u6B63\u5728\u8BFB\u53D6\u624B\u8BB0\u6B63\u6587\"\n style={[styles.loading, { backgroundColor: loadingBackground }]}\n >\n <ActivityIndicator color={loadingColor} />\n </View>\n ) : null}\n </View>\n );\n}\n\nconst styles = StyleSheet.create({\n root: { flex: 1, minHeight: 0 },\n loading: { ...StyleSheet.absoluteFillObject, alignItems: 'center', justifyContent: 'center' },\n});\n", "import React, { useCallback, useEffect, useRef, useState } from 'react';\nimport {\n ActivityIndicator,\n AppState,\n Pressable,\n ScrollView,\n StyleSheet,\n Text,\n View,\n} from 'react-native';\nimport { useTheme } from '@bardway/trpg-ui-native';\nimport { resolveTypographyTextStyle } from '@bardway/trpg-ui-native';\nimport { ThemedButton } from '@bardway/trpg-ui-native';\n\nimport { useNotebookWorkspace, type WorkspaceInput } from '@bardway/trpg-notebook/react';\nimport { NotebookReaderFrame } from './NotebookReaderFrame.js';\n\nexport interface NotebookPageProps extends WorkspaceInput {\n accountId: string;\n sessionId: string;\n isActive: boolean;\n}\n\nexport const NotebookPage = React.memo(function NotebookPage({\n accountId,\n sessionId,\n isActive,\n repository,\n importMarkdown,\n diagnostics,\n memoTemplate,\n}: NotebookPageProps) {\n const { theme, typography } = useTheme();\n const state = useNotebookWorkspace({\n accountId,\n sessionId,\n repository,\n importMarkdown,\n diagnostics,\n memoTemplate,\n });\n const documentTabs = [...(state.workspace?.documents ?? [])].sort(\n (left, right) => Number(right.kind === 'memo') - Number(left.kind === 'memo'),\n );\n const flushWorkspace = state.flush;\n const [tool, setTool] = useState<'pen' | 'eraser'>('pen');\n const [retryToken, setRetryToken] = useState(0);\n const [appActive, setAppActive] = useState(\n AppState.currentState !== 'background' && AppState.currentState !== 'inactive',\n );\n const tabScrollRef = useRef<ScrollView>(null);\n const tabPositions = useRef(new Map<string, number>());\n const tabWidthRef = useRef(0);\n const uiText = resolveTypographyTextStyle(typography, {\n role: 'ui',\n fontSize: 13,\n metric: 'control',\n centerVertically: true,\n });\n const bodyText = resolveTypographyTextStyle(typography, {\n role: 'body',\n fontSize: 13,\n metric: 'body',\n });\n\n const revealTab = useCallback((id: string): void => {\n const x = tabPositions.current.get(id);\n if (x !== undefined)\n tabScrollRef.current?.scrollTo({\n x: Math.max(0, x - tabWidthRef.current / 3),\n animated: false,\n });\n }, []);\n\n useEffect(() => {\n if (state.document) revealTab(state.document.id);\n }, [state.document?.id, state.document, revealTab]);\n\n useEffect(() => {\n setTool('pen');\n }, [accountId, sessionId]);\n useEffect(() => {\n const subscription = AppState.addEventListener('change', (next) => {\n setAppActive(next === 'active');\n if (next !== 'active') void flushWorkspace();\n });\n return () => subscription.remove();\n }, [flushWorkspace]);\n\n const retry = (): void => {\n if (state.failedSelectionId) void state.selectDocument(state.failedSelectionId);\n else if (!state.document) state.reload();\n else {\n state.prepareRetry();\n void flushWorkspace();\n setRetryToken((value) => value + 1);\n }\n };\n const hasCurrentWorkspace =\n state.workspace?.context.accountId === accountId &&\n state.workspace.context.sessionId === sessionId;\n const readerLoading = state.loading || (state.workspace !== null && !hasCurrentWorkspace);\n const hasFailure = Boolean(state.error) || state.saveState === 'failed';\n const failureMessage =\n state.error || (state.saveState === 'failed' ? '\u624B\u8BB0\u4FDD\u5B58\u5931\u8D25\uFF0C\u5DF2\u4FDD\u5B58\u5185\u5BB9\u4FDD\u7559\u3002\u8BF7\u91CD\u8BD5\u3002' : null);\n const statusLabel = hasFailure\n ? null\n : readerLoading\n ? '\u8BFB\u53D6\u4E2D'\n : state.saveState === 'saving'\n ? '\u4FDD\u5B58\u4E2D'\n : null;\n\n return (\n <View style={styles.root}>\n <View style={[styles.toolbar, { minHeight: theme.layout.density.chrome.toolbarHeight }]}>\n <ScrollView\n ref={tabScrollRef}\n horizontal\n showsHorizontalScrollIndicator={false}\n style={styles.tabScroller}\n contentContainerStyle={styles.tabs}\n onLayout={(event) => {\n tabWidthRef.current = event.nativeEvent.layout.width;\n if (state.document) revealTab(state.document.id);\n }}\n accessibilityRole=\"tablist\"\n >\n {documentTabs.map((item) => {\n const selected = state.document?.id === item.id;\n return (\n <Pressable\n key={item.id}\n accessibilityRole=\"tab\"\n accessibilityLabel={item.title}\n accessibilityState={{ selected }}\n onPress={() => {\n void state.selectDocument(item.id);\n }}\n onLayout={(event) => {\n tabPositions.current.set(item.id, event.nativeEvent.layout.x);\n if (selected) revealTab(item.id);\n }}\n style={[\n styles.tab,\n {\n minHeight: theme.layout.touch.minimumTarget,\n backgroundColor: selected\n ? theme.colors.surface.tabActive\n : theme.colors.surface.tileAlt,\n borderColor: selected\n ? theme.colors.border.active\n : theme.colors.border.default,\n },\n ]}\n >\n <Text\n numberOfLines={1}\n style={[\n uiText,\n { color: selected ? theme.colors.text.accent : theme.colors.text.primary },\n ]}\n >\n {item.title}\n </Text>\n </Pressable>\n );\n })}\n <Pressable\n onPress={() => {\n void state.importDocuments();\n }}\n disabled={state.importing || !state.workspace}\n accessibilityRole=\"button\"\n accessibilityLabel=\"\u5BFC\u5165 Markdown \u8865\u5145\u8D44\u6599\"\n accessibilityState={{\n disabled: state.importing || !state.workspace,\n busy: state.importing,\n }}\n style={[\n styles.add,\n {\n minHeight: theme.layout.touch.minimumTarget,\n minWidth: theme.layout.touch.minimumTarget,\n borderColor: theme.colors.border.default,\n backgroundColor: theme.colors.surface.tileAlt,\n },\n ]}\n >\n {state.importing ? (\n <ActivityIndicator color={theme.colors.text.accent} />\n ) : (\n <Text style={[uiText, { color: theme.colors.text.accent }]}>+</Text>\n )}\n </Pressable>\n </ScrollView>\n <View style={styles.toolGroup} accessibilityRole=\"radiogroup\">\n {(['pen', 'eraser'] as const).map((value, index) => (\n <Pressable\n key={value}\n accessibilityRole=\"radio\"\n accessibilityLabel={value === 'pen' ? '\u753B\u7B14' : '\u6574\u7B14\u6A61\u76AE'}\n accessibilityState={{ checked: tool === value }}\n onPress={() =>\n setTool((current) => (value === 'eraser' && current === 'eraser' ? 'pen' : value))\n }\n style={[\n styles.tool,\n index === 0 ? styles.toolFirst : styles.toolLast,\n {\n minHeight: theme.layout.touch.minimumTarget,\n borderColor:\n tool === value ? theme.colors.border.active : theme.colors.border.default,\n backgroundColor:\n tool === value ? theme.colors.surface.tabActive : theme.colors.surface.tileAlt,\n },\n ]}\n >\n <Text\n style={[\n uiText,\n { color: tool === value ? theme.colors.text.accent : theme.colors.text.primary },\n ]}\n >\n {value === 'pen' ? '\u753B\u7B14' : '\u6A61\u76AE'}\n </Text>\n </Pressable>\n ))}\n </View>\n </View>\n {failureMessage ? (\n <View\n accessibilityLiveRegion=\"assertive\"\n style={[\n styles.failure,\n {\n backgroundColor: theme.colors.surface.actionDanger,\n borderColor: theme.colors.status.error,\n padding: theme.layout.contentPadding.compact,\n gap: theme.layout.density.spacing.groupY,\n },\n ]}\n >\n <Text\n accessibilityRole=\"alert\"\n style={[bodyText, { color: theme.colors.text.primary, flex: 1 }]}\n >\n {failureMessage}\n </Text>\n <ThemedButton\n label=\"\u91CD\u8BD5\"\n accessibilityLabel=\"\u91CD\u8BD5\u8BFB\u53D6\u6216\u4FDD\u5B58\u624B\u8BB0\"\n onPress={retry}\n variant=\"danger\"\n style={{\n minHeight: theme.layout.touch.minimumTarget,\n minWidth: theme.layout.touch.minimumTarget,\n }}\n />\n </View>\n ) : null}\n {state.notice ? (\n <Text numberOfLines={3} style={[bodyText, { color: theme.colors.text.secondary }]}>\n {state.notice}\n </Text>\n ) : null}\n <View style={styles.reader}>\n <NotebookReaderFrame\n scopeKey={`${accountId}/${sessionId}`}\n active={isActive && appActive}\n loading={readerLoading}\n loadingColor={theme.colors.text.accent}\n loadingBackground={theme.colors.surface.overlay}\n readerProps={\n !readerLoading && hasCurrentWorkspace && state.document && state.workspace\n ? {\n context: state.workspace.context,\n document: state.document,\n isActive: isActive && appActive,\n tool,\n retryToken,\n onOperation: state.commitOperation,\n onViewport: state.saveViewport,\n onLayoutProfile: state.saveLayoutProfile,\n onError: state.reportError,\n onSaveStateChange: state.reportSaveState,\n }\n : null\n }\n >\n <View style={styles.empty}>\n <Text style={[bodyText, { color: theme.colors.text.secondary }]}>\n \u624B\u8BB0\u5C1A\u672A\u8BFB\u53D6\u3002\u6B63\u6587\u548C\u7B14\u8FF9\u4FDD\u7559\u5728\u672C\u673A\u3002\n </Text>\n <ThemedButton label=\"\u91CD\u8BD5\" onPress={state.reload} variant=\"secondary\" />\n </View>\n </NotebookReaderFrame>\n {statusLabel ? (\n <View\n pointerEvents=\"none\"\n style={[\n styles.status,\n {\n minHeight: theme.layout.touch.minimumTarget,\n backgroundColor: theme.colors.surface.panel,\n },\n ]}\n accessibilityRole=\"text\"\n accessibilityLabel={statusLabel}\n >\n <Text style={[uiText, { color: theme.colors.text.secondary }]}>{statusLabel}</Text>\n </View>\n ) : null}\n </View>\n </View>\n );\n});\n\nconst styles = StyleSheet.create({\n root: { flex: 1, minHeight: 0, gap: 6 },\n toolbar: { flexDirection: 'row', alignItems: 'center', gap: 12, flexShrink: 0 },\n tabScroller: { flex: 1, minWidth: 0 },\n tabs: { flexDirection: 'row', alignItems: 'stretch', gap: 4 },\n tab: {\n minWidth: 88,\n maxWidth: 260,\n borderWidth: 1,\n borderRadius: 5,\n paddingHorizontal: 12,\n justifyContent: 'center',\n },\n add: { borderWidth: 1, borderRadius: 5, justifyContent: 'center', alignItems: 'center' },\n toolGroup: { flexDirection: 'row', flexShrink: 0 },\n failure: { flexDirection: 'row', alignItems: 'center', borderWidth: 1, borderRadius: 5 },\n status: {\n position: 'absolute',\n top: 6,\n left: 6,\n zIndex: 1,\n borderRadius: 5,\n paddingHorizontal: 12,\n justifyContent: 'center',\n },\n tool: {\n borderWidth: 1,\n paddingHorizontal: 12,\n minWidth: 64,\n justifyContent: 'center',\n alignItems: 'center',\n },\n toolFirst: { borderTopLeftRadius: 5, borderBottomLeftRadius: 5 },\n toolLast: { borderTopRightRadius: 5, borderBottomRightRadius: 5, marginLeft: -1 },\n reader: { flex: 1, minHeight: 0, borderRadius: 5, overflow: 'hidden' },\n empty: { flex: 1, gap: 12, justifyContent: 'center', alignItems: 'center' },\n});\n", "import React from 'react';\nimport { MarkdownView } from './reader/MarkdownView.js';\nimport type { MarkdownVariant } from '@bardway/trpg-markdown/protocol';\n\ninterface AppMarkdownProps {\n children: string;\n variant?: MarkdownVariant;\n documentKey?: string;\n}\n\n/** Compatibility entry for a single body section inside a native scroll owner. */\nexport const AppMarkdown = React.memo(function AppMarkdown({\n children,\n variant = 'body',\n documentKey,\n}: AppMarkdownProps) {\n const instanceKey = React.useId();\n return (\n <MarkdownView\n documentKey={documentKey ?? `app-markdown:${instanceKey}`}\n markdown={children}\n variant={variant}\n surface=\"app\"\n scrollMode=\"intrinsic\"\n />\n );\n});\n", "import React, { useCallback, useMemo, useRef, useState } from 'react';\nimport { View, type StyleProp, type ViewStyle } from 'react-native';\n\nimport { MarkdownBlocksView } from './reader/MarkdownView.js';\nimport type { MarkdownBlockMetric, MarkdownVariant } from '@bardway/trpg-markdown/protocol';\n\nexport interface AppMarkdownSection {\n id: string;\n markdown: string;\n header?: React.ReactNode;\n headerHeight?: number;\n leading?: React.ReactNode;\n variant?: MarkdownVariant;\n fragments?: readonly string[];\n}\n\ninterface AppMarkdownSectionsProps {\n documentKey: string;\n sections: readonly AppMarkdownSection[];\n variant?: MarkdownVariant;\n leadingWidth?: number;\n columns?: number;\n style?: StyleProp<ViewStyle>;\n}\n\nconst EMPTY_HEADER_HEIGHTS: Readonly<Record<string, number>> = {};\n\n/** One reader per native panel, with native labels in measured reserved spaces. */\nexport const AppMarkdownSections = React.memo(function AppMarkdownSections({\n documentKey,\n sections,\n variant = 'body',\n leadingWidth = 0,\n columns = 1,\n style,\n}: AppMarkdownSectionsProps) {\n const activeDocumentKey = useRef(documentKey);\n activeDocumentKey.current = documentKey;\n const [measurement, setMeasurement] = useState<{\n documentKey: string;\n metrics: readonly MarkdownBlockMetric[];\n } | null>(null);\n const [headerMeasurement, setHeaderMeasurement] = useState<{\n documentKey: string;\n heights: Readonly<Record<string, number>>;\n }>({ documentKey, heights: {} });\n const headerHeights =\n headerMeasurement.documentKey === documentKey\n ? headerMeasurement.heights\n : EMPTY_HEADER_HEIGHTS;\n const blocks = useMemo(\n () =>\n sections.map((section) => ({\n id: section.id,\n markdown: section.markdown,\n variant: section.variant ?? variant,\n beforeSpace: Math.max(0, section.headerHeight ?? 0, headerHeights[section.id] ?? 0),\n fragments: section.fragments,\n })),\n [sections, variant, headerHeights],\n );\n const onBlockMetrics = useCallback(\n (metrics: readonly MarkdownBlockMetric[]) => {\n if (activeDocumentKey.current !== documentKey) return;\n setMeasurement((current) => {\n if (\n current?.documentKey === documentKey &&\n current.metrics.length === metrics.length &&\n current.metrics.every(\n (value, index) =>\n value.id === metrics[index].id &&\n value.top === metrics[index].top &&\n value.height === metrics[index].height &&\n value.left === metrics[index].left &&\n value.width === metrics[index].width,\n )\n ) {\n return current;\n }\n return { documentKey, metrics };\n });\n },\n [documentKey],\n );\n const metrics = measurement?.documentKey === documentKey ? measurement.metrics : [];\n const metricsById = new Map(metrics.map((metric) => [metric.id, metric]));\n\n return (\n <View style={style}>\n <View style={{ marginLeft: leadingWidth }}>\n <MarkdownBlocksView\n documentKey={documentKey}\n blocks={blocks}\n columns={columns}\n variant={variant}\n surface=\"app\"\n scrollMode=\"intrinsic\"\n onBlockMetrics={onBlockMetrics}\n />\n </View>\n <View\n pointerEvents=\"box-none\"\n style={{ position: 'absolute', top: 0, left: 0, right: 0, bottom: 0 }}\n >\n {sections.map((section) => {\n const metric = metricsById.get(section.id);\n if (!metric) return null;\n return (\n <React.Fragment key={section.id}>\n {section.leading && leadingWidth > 0 ? (\n <View\n testID={`markdown-leading-${section.id}`}\n style={{\n position: 'absolute',\n top: metric.top,\n left: 0,\n width: leadingWidth,\n height: metric.height,\n justifyContent: 'center',\n }}\n >\n {section.leading}\n </View>\n ) : null}\n {section.header ? (\n <View\n testID={`markdown-header-${section.id}`}\n onLayout={(event) => {\n const height = Math.ceil(event.nativeEvent.layout.height);\n if (!Number.isFinite(height) || height < 0) return;\n setHeaderMeasurement((current) => {\n const heights = current.documentKey === documentKey ? current.heights : {};\n if (heights[section.id] === height) return current;\n return { documentKey, heights: { ...heights, [section.id]: height } };\n });\n }}\n style={{\n position: 'absolute',\n top: metric.top,\n left: leadingWidth + (metric.left ?? 0),\n ...(metric.width === undefined ? { right: 0 } : { width: metric.width }),\n minHeight: section.headerHeight,\n justifyContent: 'center',\n }}\n >\n {section.header}\n </View>\n ) : null}\n </React.Fragment>\n );\n })}\n </View>\n </View>\n );\n});\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["Platform", "ReaderHost", "Platform", "React", "useCallback", "useMemo", "useTheme", "jsx", "useTheme", "useMemo", "React", "MarkdownView", "useCallback", "React", "useCallback", "useEffect", "useMemo", "useRef", "useTheme", "jsx", "React", "MarkdownDocumentView", "useTheme", "useRef", "useMemo", "useEffect", "useCallback", "useRef", "View", "jsx", "jsxs", "useRef", "View", "React", "useCallback", "useEffect", "useRef", "useState", "ActivityIndicator", "StyleSheet", "Text", "View", "useTheme", "resolveTypographyTextStyle", "ThemedButton", "jsx", "jsxs", "React", "NotebookPage", "useTheme", "useState", "useRef", "resolveTypographyTextStyle", "useCallback", "useEffect", "View", "styles", "Text", "ActivityIndicator", "ThemedButton", "StyleSheet", "React", "jsx", "React", "AppMarkdown", "React", "useCallback", "useMemo", "useRef", "useState", "View", "jsx", "jsxs", "React", "AppMarkdownSections", "useRef", "useState", "useMemo", "useCallback", "metrics", "View"]
|
|
7
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { NotebookContext, NotebookDocument, NotebookLayoutProfile, NotebookOperation, NotebookSaveAcknowledgement, NotebookViewport } from '@bardway/trpg-notebook';
|
|
3
|
+
export interface MarkdownDocumentViewProps {
|
|
4
|
+
context: NotebookContext;
|
|
5
|
+
document: NotebookDocument;
|
|
6
|
+
isActive: boolean;
|
|
7
|
+
tool: 'pen' | 'eraser';
|
|
8
|
+
retryToken?: number;
|
|
9
|
+
onOperation(operation: NotebookOperation): Promise<NotebookSaveAcknowledgement>;
|
|
10
|
+
onViewport(viewport: NotebookViewport): Promise<void> | void;
|
|
11
|
+
onLayoutProfile(profile: NotebookLayoutProfile, viewport?: NotebookViewport): Promise<void>;
|
|
12
|
+
onError(message: string): void;
|
|
13
|
+
onReady?(): void;
|
|
14
|
+
onSaveStateChange?(state: 'saved' | 'saving' | 'failed'): void;
|
|
15
|
+
}
|
|
16
|
+
export declare const MarkdownDocumentView: React.NamedExoticComponent<MarkdownDocumentViewProps>;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { MarkdownBlock, MarkdownBlockMetric, MarkdownScrollMode, MarkdownSurface, MarkdownTheme, MarkdownVariant } from '@bardway/trpg-markdown/protocol';
|
|
3
|
+
export interface MarkdownViewProps {
|
|
4
|
+
documentKey: string;
|
|
5
|
+
markdown?: string;
|
|
6
|
+
blocks?: readonly MarkdownBlock[];
|
|
7
|
+
variant?: MarkdownVariant;
|
|
8
|
+
surface?: MarkdownSurface;
|
|
9
|
+
scrollMode?: MarkdownScrollMode;
|
|
10
|
+
columns?: number;
|
|
11
|
+
onBlockMetrics?(metrics: readonly MarkdownBlockMetric[]): void;
|
|
12
|
+
}
|
|
13
|
+
export declare const useMarkdownTheme: (surface: MarkdownSurface) => MarkdownTheme;
|
|
14
|
+
export declare const MarkdownView: React.NamedExoticComponent<MarkdownViewProps>;
|
|
15
|
+
export declare const MarkdownBlocksView: React.NamedExoticComponent<MarkdownViewProps>;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export declare const isRecord: (value: unknown) => value is Record<string, unknown>;
|
|
3
|
+
export interface ReaderHostHandle {
|
|
4
|
+
send(type: string, payload?: unknown): void;
|
|
5
|
+
}
|
|
6
|
+
interface ReaderHostProps {
|
|
7
|
+
documentKey: string;
|
|
8
|
+
payload: unknown;
|
|
9
|
+
active?: boolean;
|
|
10
|
+
viewport?: boolean;
|
|
11
|
+
onEvent(type: string, payload: unknown, handle: ReaderHostHandle): void;
|
|
12
|
+
onError?(message: string): void;
|
|
13
|
+
}
|
|
14
|
+
export declare const ReaderHost: React.NamedExoticComponent<ReaderHostProps>;
|
|
15
|
+
export {};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export interface KeyValueDriver {
|
|
2
|
+
getString(key: string): string | undefined;
|
|
3
|
+
set(key: string, value: string): void;
|
|
4
|
+
delete(key: string): void;
|
|
5
|
+
}
|
|
6
|
+
export interface AsyncKeyValueDriver {
|
|
7
|
+
getString(key: string): Promise<string | null>;
|
|
8
|
+
set(key: string, value: string): Promise<void>;
|
|
9
|
+
delete(key: string): Promise<void>;
|
|
10
|
+
}
|
|
11
|
+
export declare function createMMKVDriver(instance: {
|
|
12
|
+
getString(key: string): string | undefined;
|
|
13
|
+
set(key: string, value: string): void;
|
|
14
|
+
remove(key: string): boolean;
|
|
15
|
+
}): KeyValueDriver;
|
|
16
|
+
export interface SecureStorePort {
|
|
17
|
+
isAvailableAsync(): Promise<boolean>;
|
|
18
|
+
getItemAsync(key: string, options: {
|
|
19
|
+
keychainService: string;
|
|
20
|
+
}): Promise<string | null>;
|
|
21
|
+
setItemAsync(key: string, value: string, options: {
|
|
22
|
+
keychainService: string;
|
|
23
|
+
}): Promise<void>;
|
|
24
|
+
deleteItemAsync(key: string, options: {
|
|
25
|
+
keychainService: string;
|
|
26
|
+
}): Promise<void>;
|
|
27
|
+
}
|
|
28
|
+
export declare function createSecureStorageDriver(port: SecureStorePort, service: string): AsyncKeyValueDriver;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// src/storage-drivers.ts
|
|
2
|
+
function createMMKVDriver(instance) {
|
|
3
|
+
return {
|
|
4
|
+
getString: (key) => instance.getString(key),
|
|
5
|
+
set: (key, value) => instance.set(key, value),
|
|
6
|
+
delete: (key) => {
|
|
7
|
+
instance.remove(key);
|
|
8
|
+
}
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
function createSecureStorageDriver(port, service) {
|
|
12
|
+
const options = { keychainService: service };
|
|
13
|
+
async function available() {
|
|
14
|
+
if (!await port.isAvailableAsync()) throw new Error("Secure storage unavailable");
|
|
15
|
+
}
|
|
16
|
+
return {
|
|
17
|
+
async getString(key) {
|
|
18
|
+
await available();
|
|
19
|
+
return port.getItemAsync(key, options);
|
|
20
|
+
},
|
|
21
|
+
async set(key, value) {
|
|
22
|
+
await available();
|
|
23
|
+
await port.setItemAsync(key, value, options);
|
|
24
|
+
},
|
|
25
|
+
async delete(key) {
|
|
26
|
+
await available();
|
|
27
|
+
await port.deleteItemAsync(key, options);
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
export {
|
|
32
|
+
createMMKVDriver,
|
|
33
|
+
createSecureStorageDriver
|
|
34
|
+
};
|
|
35
|
+
//# sourceMappingURL=storage-drivers.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/storage-drivers.ts"],
|
|
4
|
+
"sourcesContent": ["export interface KeyValueDriver {\n getString(key: string): string | undefined;\n set(key: string, value: string): void;\n delete(key: string): void;\n}\nexport interface AsyncKeyValueDriver {\n getString(key: string): Promise<string | null>;\n set(key: string, value: string): Promise<void>;\n delete(key: string): Promise<void>;\n}\nexport function createMMKVDriver(instance: {\n getString(key: string): string | undefined;\n set(key: string, value: string): void;\n remove(key: string): boolean;\n}): KeyValueDriver {\n return {\n getString: (key) => instance.getString(key),\n set: (key, value) => instance.set(key, value),\n delete: (key) => {\n instance.remove(key);\n },\n };\n}\nexport interface SecureStorePort {\n isAvailableAsync(): Promise<boolean>;\n getItemAsync(key: string, options: { keychainService: string }): Promise<string | null>;\n setItemAsync(key: string, value: string, options: { keychainService: string }): Promise<void>;\n deleteItemAsync(key: string, options: { keychainService: string }): Promise<void>;\n}\nexport function createSecureStorageDriver(\n port: SecureStorePort,\n service: string,\n): AsyncKeyValueDriver {\n const options = { keychainService: service };\n async function available() {\n if (!(await port.isAvailableAsync())) throw new Error('Secure storage unavailable');\n }\n return {\n async getString(key) {\n await available();\n return port.getItemAsync(key, options);\n },\n async set(key, value) {\n await available();\n await port.setItemAsync(key, value, options);\n },\n async delete(key) {\n await available();\n await port.deleteItemAsync(key, options);\n },\n };\n}\n"],
|
|
5
|
+
"mappings": ";AAUO,SAAS,iBAAiB,UAId;AACjB,SAAO;AAAA,IACL,WAAW,CAAC,QAAQ,SAAS,UAAU,GAAG;AAAA,IAC1C,KAAK,CAAC,KAAK,UAAU,SAAS,IAAI,KAAK,KAAK;AAAA,IAC5C,QAAQ,CAAC,QAAQ;AACf,eAAS,OAAO,GAAG;AAAA,IACrB;AAAA,EACF;AACF;AAOO,SAAS,0BACd,MACA,SACqB;AACrB,QAAM,UAAU,EAAE,iBAAiB,QAAQ;AAC3C,iBAAe,YAAY;AACzB,QAAI,CAAE,MAAM,KAAK,iBAAiB,EAAI,OAAM,IAAI,MAAM,4BAA4B;AAAA,EACpF;AACA,SAAO;AAAA,IACL,MAAM,UAAU,KAAK;AACnB,YAAM,UAAU;AAChB,aAAO,KAAK,aAAa,KAAK,OAAO;AAAA,IACvC;AAAA,IACA,MAAM,IAAI,KAAK,OAAO;AACpB,YAAM,UAAU;AAChB,YAAM,KAAK,aAAa,KAAK,OAAO,OAAO;AAAA,IAC7C;AAAA,IACA,MAAM,OAAO,KAAK;AAChB,YAAM,UAAU;AAChB,YAAM,KAAK,gBAAgB,KAAK,OAAO;AAAA,IACzC;AAAA,EACF;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
package org.bardway.trpg.reader
|
|
2
|
+
|
|
3
|
+
import android.graphics.BitmapFactory
|
|
4
|
+
import android.graphics.Rect
|
|
5
|
+
import android.net.Uri
|
|
6
|
+
import android.view.View
|
|
7
|
+
import android.view.ViewGroup
|
|
8
|
+
import android.webkit.WebResourceRequest
|
|
9
|
+
import android.webkit.WebResourceResponse
|
|
10
|
+
import android.webkit.WebView
|
|
11
|
+
import androidx.webkit.WebViewAssetLoader
|
|
12
|
+
import androidx.webkit.WebViewCompat
|
|
13
|
+
import androidx.webkit.WebViewFeature
|
|
14
|
+
import com.facebook.react.ReactPackage
|
|
15
|
+
import com.facebook.react.bridge.NativeModule
|
|
16
|
+
import com.facebook.react.bridge.ReactContextBaseJavaModule
|
|
17
|
+
import com.facebook.react.bridge.ReactMethod
|
|
18
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
19
|
+
import com.facebook.react.uimanager.UIManagerHelper
|
|
20
|
+
import com.facebook.react.uimanager.ThemedReactContext
|
|
21
|
+
import com.facebook.react.uimanager.ViewManager
|
|
22
|
+
import com.reactnativecommunity.webview.RNCWebViewClient
|
|
23
|
+
import com.reactnativecommunity.webview.RNCWebViewManager
|
|
24
|
+
import com.reactnativecommunity.webview.RNCWebViewWrapper
|
|
25
|
+
import java.io.ByteArrayInputStream
|
|
26
|
+
import java.net.HttpURLConnection
|
|
27
|
+
import java.net.URL
|
|
28
|
+
|
|
29
|
+
class TrpgMarkdownWebViewPackage : ReactPackage {
|
|
30
|
+
override fun createNativeModules(context: ReactApplicationContext): List<NativeModule> = listOf(TrpgMarkdownReaderBridge(context))
|
|
31
|
+
override fun createViewManagers(context: ReactApplicationContext): List<ViewManager<*, *>> = listOf(TrpgMarkdownWebViewManager())
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Reveal an intrinsic reader anchor through its existing native scroll parent. */
|
|
35
|
+
private class TrpgMarkdownReaderBridge(private val context: ReactApplicationContext) : ReactContextBaseJavaModule(context) {
|
|
36
|
+
override fun getName() = "TrpgMarkdownReaderBridge"
|
|
37
|
+
@ReactMethod fun revealAnchor(containerTag: Int, logicalY: Double) {
|
|
38
|
+
if (!logicalY.isFinite() || logicalY < 0 || logicalY > 1_000_000) return
|
|
39
|
+
context.runOnUiQueueThread {
|
|
40
|
+
try {
|
|
41
|
+
val container = UIManagerHelper.getUIManagerForReactTag(context, containerTag)?.resolveView(containerTag) ?: return@runOnUiQueueThread
|
|
42
|
+
fun findWebView(view: View): WebView? {
|
|
43
|
+
if (view is WebView) return view
|
|
44
|
+
if (view is ViewGroup) for (index in 0 until view.childCount) findWebView(view.getChildAt(index))?.let { return it }
|
|
45
|
+
return null
|
|
46
|
+
}
|
|
47
|
+
val webView = findWebView(container) ?: return@runOnUiQueueThread
|
|
48
|
+
val y = (logicalY * context.resources.displayMetrics.density).toInt()
|
|
49
|
+
webView.requestRectangleOnScreen(Rect(0, y, webView.width, y + (32 * context.resources.displayMetrics.density).toInt()), false)
|
|
50
|
+
} catch (_: RuntimeException) { /* The host may have unmounted before this command. */ }
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
class TrpgMarkdownWebViewManager : RNCWebViewManager() {
|
|
56
|
+
override fun getName() = "TrpgMarkdownWebView"
|
|
57
|
+
override fun addEventEmitters(context: ThemedReactContext, view: RNCWebViewWrapper) {
|
|
58
|
+
view.webView.webViewClient = ReaderClient(context)
|
|
59
|
+
view.webView.setDownloadListener(null)
|
|
60
|
+
}
|
|
61
|
+
override fun setMessagingEnabled(view: RNCWebViewWrapper, value: Boolean) {
|
|
62
|
+
super.setMessagingEnabled(view, value)
|
|
63
|
+
val webView = view.webView
|
|
64
|
+
if (!WebViewFeature.isFeatureSupported(WebViewFeature.WEB_MESSAGE_LISTENER)) {
|
|
65
|
+
webView.removeJavascriptInterface("ReactNativeWebView")
|
|
66
|
+
return
|
|
67
|
+
}
|
|
68
|
+
WebViewCompat.removeWebMessageListener(webView, "ReactNativeWebView")
|
|
69
|
+
if (value) WebViewCompat.addWebMessageListener(webView, "ReactNativeWebView", setOf("https://appassets.androidplatform.net")) { source, message, origin, isMainFrame, _ ->
|
|
70
|
+
if (isMainFrame && origin.toString() == "https://appassets.androidplatform.net" && source.url == "https://appassets.androidplatform.net/reader/index.html" && (message.data?.length ?: Int.MAX_VALUE) <= 100_000)
|
|
71
|
+
webView.onMessage(message.data, "https://appassets.androidplatform.net/reader/index.html")
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
private class ReaderClient(private val context: ThemedReactContext) : RNCWebViewClient() {
|
|
77
|
+
private val origin = "appassets.androidplatform.net"
|
|
78
|
+
private fun response(mime: String, bytes: ByteArray): WebResourceResponse = WebResourceResponse(mime, "UTF-8", ByteArrayInputStream(bytes)).apply {
|
|
79
|
+
responseHeaders = mapOf("Cache-Control" to "no-store", "X-Content-Type-Options" to "nosniff")
|
|
80
|
+
}
|
|
81
|
+
private fun blocked() = response("text/plain", ByteArray(0))
|
|
82
|
+
private val loader = WebViewAssetLoader.Builder()
|
|
83
|
+
.addPathHandler("/reader/") { name ->
|
|
84
|
+
if (name !in setOf("index.html", "reader.js", "reader.css")) blocked()
|
|
85
|
+
else try { response(if (name.endsWith(".js")) "text/javascript" else if (name.endsWith(".css")) "text/css" else "text/html", context.assets.open("markdown-reader/$name").use { it.readBytes() }) } catch (_: Exception) { blocked() }
|
|
86
|
+
}
|
|
87
|
+
.addPathHandler("/fonts/") { name ->
|
|
88
|
+
val font = TrpgReaderFontResources.fonts[name]
|
|
89
|
+
if (font == null) blocked() else {
|
|
90
|
+
val id = context.resources.getIdentifier(font, "font", context.packageName)
|
|
91
|
+
if (id == 0) blocked() else WebResourceResponse("font/ttf", null, context.resources.openRawResource(id))
|
|
92
|
+
}
|
|
93
|
+
}.build()
|
|
94
|
+
|
|
95
|
+
override fun shouldOverrideUrlLoading(view: WebView, request: WebResourceRequest): Boolean =
|
|
96
|
+
request.url.scheme != "https" || request.url.host != origin || request.url.path != "/reader/index.html"
|
|
97
|
+
override fun shouldOverrideUrlLoading(view: WebView, url: String): Boolean =
|
|
98
|
+
Uri.parse(url).let { it.scheme != "https" || it.host != origin || it.path != "/reader/index.html" }
|
|
99
|
+
|
|
100
|
+
override fun shouldInterceptRequest(view: WebView, request: WebResourceRequest): WebResourceResponse = intercept(request.url)
|
|
101
|
+
override fun shouldInterceptRequest(view: WebView, url: String): WebResourceResponse = intercept(Uri.parse(url))
|
|
102
|
+
|
|
103
|
+
private fun intercept(uri: Uri): WebResourceResponse {
|
|
104
|
+
if (uri.scheme != "https" || uri.host != origin) return blocked()
|
|
105
|
+
if (uri.path == "/image") return loadImage(uri.getQueryParameter("url"))
|
|
106
|
+
return loader.shouldInterceptRequest(uri) ?: blocked()
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
private fun loadImage(value: String?): WebResourceResponse {
|
|
110
|
+
if (value == null || value.length > 8192) return blocked()
|
|
111
|
+
var next = value
|
|
112
|
+
try {
|
|
113
|
+
repeat(4) {
|
|
114
|
+
val parsed = Uri.parse(next)
|
|
115
|
+
if (parsed.scheme != "https" || parsed.host == null || parsed.host == origin || parsed.userInfo != null) return blocked()
|
|
116
|
+
val connection = URL(next).openConnection() as HttpURLConnection
|
|
117
|
+
connection.instanceFollowRedirects = false
|
|
118
|
+
connection.connectTimeout = 8000
|
|
119
|
+
connection.readTimeout = 8000
|
|
120
|
+
connection.setRequestProperty("Accept", "image/png,image/jpeg,image/webp")
|
|
121
|
+
connection.setRequestProperty("Cookie", "")
|
|
122
|
+
try {
|
|
123
|
+
if (connection.responseCode in 300..399) {
|
|
124
|
+
next = URL(URL(next), connection.getHeaderField("Location") ?: return blocked()).toString()
|
|
125
|
+
} else {
|
|
126
|
+
if (connection.responseCode != 200 || connection.contentLengthLong > 4 * 1024 * 1024) return blocked()
|
|
127
|
+
val output = java.io.ByteArrayOutputStream()
|
|
128
|
+
connection.inputStream.use { input ->
|
|
129
|
+
val buffer = ByteArray(16384)
|
|
130
|
+
var length = input.read(buffer)
|
|
131
|
+
while (length >= 0) {
|
|
132
|
+
if (output.size() + length > 4 * 1024 * 1024) return blocked()
|
|
133
|
+
output.write(buffer, 0, length)
|
|
134
|
+
length = input.read(buffer)
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
val bytes = output.toByteArray()
|
|
138
|
+
val options = BitmapFactory.Options().apply { inJustDecodeBounds = true }
|
|
139
|
+
BitmapFactory.decodeByteArray(bytes, 0, bytes.size, options)
|
|
140
|
+
val mime = options.outMimeType
|
|
141
|
+
if (mime !in setOf("image/png", "image/jpeg", "image/webp") || options.outWidth <= 0 || options.outHeight <= 0 || options.outWidth.toLong() * options.outHeight > 8_000_000) return blocked()
|
|
142
|
+
return response(mime, bytes)
|
|
143
|
+
}
|
|
144
|
+
} finally { connection.disconnect() }
|
|
145
|
+
}
|
|
146
|
+
} catch (_: Exception) { return blocked() }
|
|
147
|
+
return blocked()
|
|
148
|
+
}
|
|
149
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,78 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bardway/trpg-notebook-native",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"description": "
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Rule-independent notebook-native extracted from MingriAgency",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/bardway/mingriAgency_TRPGEngine.git",
|
|
10
|
+
"directory": "packages/notebook-native"
|
|
11
|
+
},
|
|
12
|
+
"publishConfig": {
|
|
13
|
+
"registry": "https://registry.npmjs.org/",
|
|
14
|
+
"access": "public"
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist",
|
|
18
|
+
"native",
|
|
19
|
+
"expo-module.config.json",
|
|
20
|
+
"app.plugin.cjs",
|
|
21
|
+
"LICENSE",
|
|
22
|
+
"NOTICE",
|
|
23
|
+
"android/build.gradle",
|
|
24
|
+
"android/src/main"
|
|
25
|
+
],
|
|
26
|
+
"exports": {
|
|
27
|
+
".": {
|
|
28
|
+
"types": "./dist/index.d.ts",
|
|
29
|
+
"import": "./dist/index.js",
|
|
30
|
+
"react-native": "./dist/index.js",
|
|
31
|
+
"default": "./dist/index.js"
|
|
32
|
+
},
|
|
33
|
+
"./storage-drivers": {
|
|
34
|
+
"types": "./dist/storage-drivers.d.ts",
|
|
35
|
+
"import": "./dist/storage-drivers.js",
|
|
36
|
+
"react-native": "./dist/storage-drivers.js",
|
|
37
|
+
"default": "./dist/storage-drivers.js"
|
|
38
|
+
},
|
|
39
|
+
"./chunked-secure": {
|
|
40
|
+
"types": "./dist/chunked-secure.d.ts",
|
|
41
|
+
"import": "./dist/chunked-secure.js",
|
|
42
|
+
"react-native": "./dist/chunked-secure.js",
|
|
43
|
+
"default": "./dist/chunked-secure.js"
|
|
44
|
+
},
|
|
45
|
+
"./app.plugin": "./app.plugin.cjs"
|
|
46
|
+
},
|
|
47
|
+
"main": "./dist/index.js",
|
|
48
|
+
"types": "./dist/index.d.ts",
|
|
49
|
+
"dependencies": {
|
|
50
|
+
"@bardway/trpg-notebook": "0.1.0",
|
|
51
|
+
"@bardway/trpg-markdown": "0.1.0",
|
|
52
|
+
"@bardway/trpg-ui-native": "0.1.0"
|
|
53
|
+
},
|
|
54
|
+
"peerDependencies": {
|
|
55
|
+
"expo": "~55.0.0",
|
|
56
|
+
"expo-document-picker": "~55.0.17",
|
|
57
|
+
"expo-file-system": "~55.0.26",
|
|
58
|
+
"react": "19.2.0",
|
|
59
|
+
"react-native": "0.83.10",
|
|
60
|
+
"react-native-webview": "13.16.0",
|
|
61
|
+
"@expo/config-plugins": "55.0.11"
|
|
62
|
+
},
|
|
63
|
+
"scripts": {
|
|
64
|
+
"build": "node ../../scripts/build.mjs --only=@bardway/trpg-notebook-native"
|
|
65
|
+
},
|
|
66
|
+
"trpg": {
|
|
67
|
+
"entries": {
|
|
68
|
+
"index": "src/index.ts",
|
|
69
|
+
"storage-drivers": "src/storage-drivers.ts",
|
|
70
|
+
"chunked-secure": "src/chunked-secure.ts"
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
"peerDependenciesMeta": {
|
|
74
|
+
"@expo/config-plugins": {
|
|
75
|
+
"optional": true
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|