diff --git a/apps/mobile/app.json b/apps/mobile/app.json
index bf41ebea..59400a03 100644
--- a/apps/mobile/app.json
+++ b/apps/mobile/app.json
@@ -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",
diff --git a/apps/mobile/src/components/MobileMermaid.tsx b/apps/mobile/src/components/MobileMermaid.tsx
index 55919055..c902fbb4 100644
--- a/apps/mobile/src/components/MobileMermaid.tsx
+++ b/apps/mobile/src/components/MobileMermaid.tsx
@@ -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 (
-
+
);
};
diff --git a/apps/mobile/src/lib/mobile-mermaid.test.ts b/apps/mobile/src/lib/mobile-mermaid.test.ts
index d419588b..1f022b5c 100644
--- a/apps/mobile/src/lib/mobile-mermaid.test.ts
+++ b/apps/mobile/src/lib/mobile-mermaid.test.ts
@@ -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('')).toBe(2);
expect(getMermaidSvgAspectRatio("")).toBe(1.6);
});
+
+ test("normalizes SVG 2 marker orientation for react-native-svg", () => {
+ expect(sanitizeMobileMermaidSvg('')).toBe('');
+ expect(sanitizeMobileMermaidSvg("")).toBe("");
+ expect(sanitizeMobileMermaidSvg('')).toBe('');
+ });
});
diff --git a/apps/mobile/src/lib/mobile-mermaid.ts b/apps/mobile/src/lib/mobile-mermaid.ts
index 4f1beb60..dc77afc8 100644
--- a/apps/mobile/src/lib/mobile-mermaid.ts
+++ b/apps/mobile/src/lib/mobile-mermaid.ts
@@ -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]);
diff --git a/apps/mobile/src/screens/WorkspaceScreen.tsx b/apps/mobile/src/screens/WorkspaceScreen.tsx
index ca097385..a02cea27 100644
--- a/apps/mobile/src/screens/WorkspaceScreen.tsx
+++ b/apps/mobile/src/screens/WorkspaceScreen.tsx
@@ -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 ? {
onRichEdit={(memo) => void openRichEditor(memo)}
onOpenRevisions={setRevisionMemo}
onRestore={(memo) => restoreMemoMutation.mutate(memo)}
- visible
- /> : null}
+ visible={Boolean(selectedMemoId)}
+ />
{editorRuntimeWarm ? (
{
}}
/> : null}
- {createOpen ? {
}}
onQueued={runAutomaticSync}
syncQueueScope={syncQueueScope}
- visible
- /> : null}
+ visible={createOpen}
+ />
{selectionMoveOpen ? void requestClose()} presentationStyle="fullScreen" visible={visible}>
+ void requestClose()} presentationStyle="fullScreen" visible={visible}>
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 (
-
+
{selectionMode ? (
{
+ pressScale.value = withTiming(0.985, { duration: 100 });
+ }}
+ onPressOut={() => {
+ pressScale.value = withTiming(1, { duration: 160 });
+ }}
style={[styles.memoCardContent, listDensity === "compact" && styles.memoCardContentCompact, selectionMode && styles.memoCardContentWithSelection]}
>
@@ -5345,7 +5365,7 @@ const MemoCard = memo(function MemoCard({
))}
-
+
);
}, (previous, next) =>
previous.memo === next.memo &&
diff --git a/package.json b/package.json
index a6276896..17a9a808 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "edgeever",
- "version": "1.5.15",
+ "version": "1.5.16",
"private": true,
"type": "module",
"workspaces": [