fix: polish mobile transitions and Mermaid rendering
This commit is contained in:
1 parent
2cc23d419e
commit
3ef15a2b8d
6 files changed
+52
-15
No files matched your search
@@ -3,7 +3,7 @@
|
||||
"name": "EdgeEver",
|
||||
"slug": "edgeever",
|
||||
"scheme": "edgeever",
|
||||
"version": "1.5.14",
|
||||
"version": "1.5.16",
|
||||
"runtimeVersion": {
|
||||
"policy": "appVersion"
|
||||
},
|
||||
@@ -28,7 +28,7 @@
|
||||
},
|
||||
"android": {
|
||||
"package": "org.edgeever.mobile",
|
||||
"versionCode": 35,
|
||||
"versionCode": 36,
|
||||
"adaptiveIcon": {
|
||||
"backgroundColor": "#d1fae5",
|
||||
"backgroundImage": "./assets/icon.png",
|
||||
|
||||
@@ -2,7 +2,7 @@ import { createContext, useCallback, useContext, useEffect, useMemo, useRef, use
|
||||
import { StyleSheet, Text, useWindowDimensions, View } from "react-native";
|
||||
import { SvgXml } from "react-native-svg";
|
||||
import LocalTiptapEditor from "./LocalTiptapEditor";
|
||||
import { getMermaidSvgAspectRatio } from "../lib/mobile-mermaid";
|
||||
import { getMermaidSvgAspectRatio, sanitizeMobileMermaidSvg } from "../lib/mobile-mermaid";
|
||||
|
||||
type MermaidResult = { source: string; svg: string | null };
|
||||
type MermaidContextValue = {
|
||||
@@ -109,9 +109,10 @@ export const MobileMermaidDiagram = ({
|
||||
const aspectRatio = getMermaidSvgAspectRatio(svg);
|
||||
const availableWidth = Math.max(1, width - 32);
|
||||
const height = Math.min(440, Math.max(120, availableWidth / aspectRatio));
|
||||
const nativeSvg = sanitizeMobileMermaidSvg(svg);
|
||||
return (
|
||||
<View accessibilityLabel={locale === "en-US" ? "Mermaid diagram" : "Mermaid 图表"} accessible style={[styles.diagram, theme === "dark" && styles.diagramDark, { height }]}>
|
||||
<SvgXml height="100%" width="100%" xml={svg} />
|
||||
<SvgXml height="100%" width="100%" xml={nativeSvg} />
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -2,6 +2,7 @@ import { describe, expect, test } from "bun:test";
|
||||
import {
|
||||
getMermaidSvgAspectRatio,
|
||||
getMobileMarkdownFenceLanguage,
|
||||
sanitizeMobileMermaidSvg,
|
||||
trimMobileMarkdownFenceContent,
|
||||
} from "./mobile-mermaid";
|
||||
|
||||
@@ -20,4 +21,10 @@ describe("mobile Mermaid rendering", () => {
|
||||
expect(getMermaidSvgAspectRatio('<svg viewBox="0 0 800 400"></svg>')).toBe(2);
|
||||
expect(getMermaidSvgAspectRatio("<svg></svg>")).toBe(1.6);
|
||||
});
|
||||
|
||||
test("normalizes SVG 2 marker orientation for react-native-svg", () => {
|
||||
expect(sanitizeMobileMermaidSvg('<marker orient="auto-start-reverse" />')).toBe('<marker orient="auto" />');
|
||||
expect(sanitizeMobileMermaidSvg("<marker orient='auto-start-reverse' />")).toBe("<marker orient='auto' />");
|
||||
expect(sanitizeMobileMermaidSvg('<marker orient="45" />')).toBe('<marker orient="45" />');
|
||||
});
|
||||
});
|
||||
@@ -4,6 +4,15 @@ export const getMobileMarkdownFenceLanguage = (sourceInfo: unknown) =>
|
||||
export const trimMobileMarkdownFenceContent = (content: string) =>
|
||||
content.endsWith("\n") ? content.slice(0, -1) : content;
|
||||
|
||||
/**
|
||||
* Mermaid uses the SVG 2 `auto-start-reverse` marker orientation for arrows.
|
||||
* Browsers support it, but react-native-svg parses marker `orient` as a
|
||||
* numeric angle and crashes while drawing the SVG. `auto` keeps the arrow
|
||||
* direction compatible on native without changing the Mermaid source.
|
||||
*/
|
||||
export const sanitizeMobileMermaidSvg = (svg: string) =>
|
||||
svg.replace(/(\borient\s*=\s*)(["'])auto-start-reverse\2/gi, "$1$2auto$2");
|
||||
|
||||
export const getMermaidSvgAspectRatio = (svg: string) => {
|
||||
const viewBox = /viewBox=["']\s*[-\d.]+\s+[-\d.]+\s+([\d.]+)\s+([\d.]+)\s*["']/i.exec(svg);
|
||||
const width = Number(viewBox?.[1]);
|
||||
|
||||
@@ -77,7 +77,7 @@ import {
|
||||
type ViewStyle,
|
||||
} from "react-native";
|
||||
import { Gesture, GestureDetector } from "react-native-gesture-handler";
|
||||
import Animated, { runOnJS, useAnimatedStyle, useSharedValue, withTiming } from "react-native-reanimated";
|
||||
import Animated, { FadeInDown, FadeOutUp, LinearTransition, runOnJS, useAnimatedStyle, useSharedValue, withTiming } from "react-native-reanimated";
|
||||
import { SafeAreaView, useSafeAreaInsets } from "react-native-safe-area-context";
|
||||
import { Alert, Pressable, Text, TextInput } from "../components/LocalizedText";
|
||||
import Markdown, { type ASTNode, type RenderRules } from "react-native-markdown-display";
|
||||
@@ -1115,7 +1115,7 @@ export const WorkspaceScreen = () => {
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{selectedMemoId ? <MemoDetailModal
|
||||
<MemoDetailModal
|
||||
isDeleting={deleteMemoMutation.isPending}
|
||||
isLoading={memoDetailQuery.isLoading}
|
||||
isRestoring={restoreMemoMutation.isPending}
|
||||
@@ -1127,8 +1127,8 @@ export const WorkspaceScreen = () => {
|
||||
onRichEdit={(memo) => void openRichEditor(memo)}
|
||||
onOpenRevisions={setRevisionMemo}
|
||||
onRestore={(memo) => restoreMemoMutation.mutate(memo)}
|
||||
visible
|
||||
/> : null}
|
||||
visible={Boolean(selectedMemoId)}
|
||||
/>
|
||||
|
||||
{editorRuntimeWarm ? (
|
||||
<EditorRuntimePrewarm
|
||||
@@ -1163,7 +1163,7 @@ export const WorkspaceScreen = () => {
|
||||
}}
|
||||
/> : null}
|
||||
|
||||
{createOpen ? <CreateMemoModal
|
||||
<CreateMemoModal
|
||||
baseUrl={session?.baseUrl ?? ""}
|
||||
dataScope={dataScope}
|
||||
defaultNotebookId={defaultMemoNotebookId}
|
||||
@@ -1177,8 +1177,8 @@ export const WorkspaceScreen = () => {
|
||||
}}
|
||||
onQueued={runAutomaticSync}
|
||||
syncQueueScope={syncQueueScope}
|
||||
visible
|
||||
/> : null}
|
||||
visible={createOpen}
|
||||
/>
|
||||
|
||||
{selectionMoveOpen ? <MoveSelectionModal
|
||||
bottomOffset={58 + safeAreaInsets.bottom}
|
||||
@@ -2383,7 +2383,7 @@ const CreateMemoModal = ({
|
||||
) : null, [baseUrl, draftLoaded, loadEditorResource, resolvedLocale, resolvedTheme]);
|
||||
|
||||
return (
|
||||
<Modal animationType="none" onRequestClose={() => void requestClose()} presentationStyle="fullScreen" visible={visible}>
|
||||
<Modal animationType="slide" onRequestClose={() => void requestClose()} presentationStyle="fullScreen" visible={visible}>
|
||||
<SafeAreaView style={styles.createMemoSafeArea}>
|
||||
<View style={styles.createMemoHeader}>
|
||||
<Pressable accessibilityLabel="返回" accessibilityRole="button" disabled={createMutation.isPending || imageOperation !== "idle"} onPress={() => void requestClose()} style={styles.createMemoBackButton}>
|
||||
@@ -5299,9 +5299,23 @@ const MemoCard = memo(function MemoCard({
|
||||
}) {
|
||||
const localePreference = useMobileLocalePreference();
|
||||
const memoTitle = memo.title?.trim() || DEFAULT_MEMO_TITLE;
|
||||
const pressScale = useSharedValue(1);
|
||||
const pressAnimatedStyle = useAnimatedStyle(() => ({
|
||||
transform: [{ scale: pressScale.value }],
|
||||
}));
|
||||
|
||||
return (
|
||||
<View style={[styles.memoCard, listDensity === "compact" && styles.memoCardCompact, selected && styles.memoCardSelected]}>
|
||||
<Animated.View
|
||||
entering={FadeInDown.duration(260).springify().damping(18)}
|
||||
exiting={FadeOutUp.duration(220)}
|
||||
layout={LinearTransition.duration(220)}
|
||||
style={[
|
||||
styles.memoCard,
|
||||
listDensity === "compact" && styles.memoCardCompact,
|
||||
selected && styles.memoCardSelected,
|
||||
pressAnimatedStyle,
|
||||
]}
|
||||
>
|
||||
{selectionMode ? (
|
||||
<Pressable
|
||||
accessibilityLabel={`${selected ? "取消选择" : "选择"} ${memoTitle}`}
|
||||
@@ -5321,6 +5335,12 @@ const MemoCard = memo(function MemoCard({
|
||||
delayLongPress={520}
|
||||
onLongPress={onLongPress}
|
||||
onPress={onPress}
|
||||
onPressIn={() => {
|
||||
pressScale.value = withTiming(0.985, { duration: 100 });
|
||||
}}
|
||||
onPressOut={() => {
|
||||
pressScale.value = withTiming(1, { duration: 160 });
|
||||
}}
|
||||
style={[styles.memoCardContent, listDensity === "compact" && styles.memoCardContentCompact, selectionMode && styles.memoCardContentWithSelection]}
|
||||
>
|
||||
<View style={styles.memoCardTop}>
|
||||
@@ -5345,7 +5365,7 @@ const MemoCard = memo(function MemoCard({
|
||||
))}
|
||||
</View>
|
||||
</Pressable>
|
||||
</View>
|
||||
</Animated.View>
|
||||
);
|
||||
}, (previous, next) =>
|
||||
previous.memo === next.memo &&
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "edgeever",
|
||||
"version": "1.5.15",
|
||||
"version": "1.5.16",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"workspaces": [
|
||||
|
||||
Reference in new issue
Block a user