fix: render app tables responsively (#76)

This commit is contained in:
tianma-if committed 2026-07-21 21:34:03 +08:00
1 parent d36de695ff
commit 4396cd787c
2 files changed
+112 -16

No files matched your search

+2 -2
View File
@@ -3,7 +3,7 @@
"name": "EdgeEver",
"slug": "edgeever",
"scheme": "edgeever",
"version": "1.3.2",
"version": "1.3.4",
"runtimeVersion": {
"policy": "appVersion"
},
@@ -28,7 +28,7 @@
},
"android": {
"package": "org.edgeever.mobile",
"versionCode": 30,
"versionCode": 31,
"adaptiveIcon": {
"backgroundColor": "#d1fae5",
"backgroundImage": "./assets/icon.png",
+110 -14
View File
@@ -70,14 +70,16 @@ import {
StyleSheet,
type StyleProp,
Switch,
useWindowDimensions,
Vibration,
View,
type ViewStyle,
} from "react-native";
import { Gesture, GestureDetector } from "react-native-gesture-handler";
import Animated, { 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 RenderRules } from "react-native-markdown-display";
import Markdown, { type ASTNode, type RenderRules } from "react-native-markdown-display";
import { SvgXml } from "react-native-svg";
import { buildGitHubFeedbackUrl, buildRevisionDiffRows, createExcerpt, docToMarkdown, docToText, getNotebookDescendantIds, markdownToDoc, resolveMemoContentDoc, type ApiToken, type AuthUser, type MemoDetail, type MemoRevision, type MemoSummary, type Notebook, type ResourceListItem, type RevisionDiffRow, type TagSummary, type TiptapDoc } from "@edgeever/shared";
import { MOBILE_UI_METRICS, getMobileCenteredScrollOffset, getMobileNotebookSearchVisibleIds, toggleMobileMemoFilterMode, toggleMobileMemoSelection } from "@edgeever/shared/mobile-ui";
@@ -133,6 +135,9 @@ const resolveEditableMemoTitle = (title?: string | null) => {
const MOBILE_APP_VERSION = Constants.expoConfig?.version ?? "0.1.2";
const GITHUB_REPOSITORY_URL = "https://github.com/tianma-if/edgeever";
const ANDROID_SYSTEM_NAVIGATION_FALLBACK = 48;
const DETAIL_CONTENT_HORIZONTAL_PADDING = 16;
const DETAIL_TABLE_FIT_COLUMN_COUNT = 3;
const DETAIL_TABLE_MIN_COLUMN_WIDTH = 132;
const formatExecutionEnvironment = (environment: string | null | undefined, localePreference: MobileLocaleMode = "system") => {
const english = isEnglishMobileLocale(localePreference);
@@ -4020,6 +4025,7 @@ const MemoDetailModal = ({
}) => {
const { session } = useSession();
const { resolvedTheme } = useMobileTheme();
const { width: viewportWidth } = useWindowDimensions();
const safeAreaInsets = useSafeAreaInsets();
const [actionsOpen, setActionsOpen] = useState(false);
const [searchOpen, setSearchOpen] = useState(false);
@@ -4031,18 +4037,71 @@ const MemoDetailModal = ({
() => resolveMobileThemeStyles(detailMarkdownStyles, resolvedTheme),
[resolvedTheme]
);
const detailMarkdownRules = useMemo<RenderRules>(() => ({
image: (node, _children, _parents, markdownStyles) => (
<AuthenticatedResourceImage
alt={String(node.attributes.alt ?? "")}
fitAspect
key={node.key}
resizeMode="contain"
source={getAuthenticatedResourceSource(String(node.attributes.src ?? ""), session)}
style={markdownStyles._VIEW_SAFE_image}
/>
),
}), [session]);
const detailMarkdownRules = useMemo<RenderRules>(() => {
const availableTableWidth = Math.max(0, viewportWidth - DETAIL_CONTENT_HORIZONTAL_PADDING * 2);
const getTableColumnCount = (node: ASTNode, parents: ASTNode[]) => {
const table = node.type === "table" ? node : parents.find((parent) => parent.type === "table");
const tableSection = table?.children.find((child) => child.type === "thead" || child.type === "tbody");
const firstRow = tableSection?.children.find((child) => child.type === "tr");
return Math.max(1, firstRow?.children.filter((child) => child.type === "th" || child.type === "td").length ?? 1);
};
const renderTableCell = (node: ASTNode, children: ReactNode[], parents: ASTNode[], markdownStyles: Record<string, StyleProp<ViewStyle>>, isHeader: boolean) => {
const columnCount = getTableColumnCount(node, parents);
const row = parents.find((parent) => parent.type === "tr");
const cellIndex = row?.children.findIndex((child) => child.key === node.key) ?? -1;
const isLastCell = cellIndex === (row?.children.length ?? 0) - 1;
const wideCellStyle = columnCount > DETAIL_TABLE_FIT_COLUMN_COUNT
? { flex: 0, width: DETAIL_TABLE_MIN_COLUMN_WIDTH }
: undefined;
return (
<View
key={node.key}
style={[
markdownStyles[isHeader ? "_VIEW_SAFE_th" : "_VIEW_SAFE_td"],
wideCellStyle,
!isLastCell && markdownStyles._VIEW_SAFE_tableCellDivider,
]}
>
{children}
</View>
);
};
return {
image: (node, _children, _parents, markdownStyles) => (
<AuthenticatedResourceImage
alt={String(node.attributes.alt ?? "")}
fitAspect
key={node.key}
resizeMode="contain"
source={getAuthenticatedResourceSource(String(node.attributes.src ?? ""), session)}
style={markdownStyles._VIEW_SAFE_image}
/>
),
table: (node, children, parents, markdownStyles) => {
const columnCount = getTableColumnCount(node, parents);
const tableWidth = columnCount > DETAIL_TABLE_FIT_COLUMN_COUNT
? columnCount * DETAIL_TABLE_MIN_COLUMN_WIDTH
: availableTableWidth;
return (
<ScrollView
contentContainerStyle={markdownStyles._VIEW_SAFE_tableScrollContent}
horizontal
key={node.key}
nestedScrollEnabled
showsHorizontalScrollIndicator={columnCount > DETAIL_TABLE_FIT_COLUMN_COUNT}
style={markdownStyles._VIEW_SAFE_tableScroll}
>
<View style={[markdownStyles._VIEW_SAFE_table, { width: tableWidth }]}>{children}</View>
</ScrollView>
);
},
td: (node, children, parents, markdownStyles) => renderTableCell(node, children, parents, markdownStyles, false),
th: (node, children, parents, markdownStyles) => renderTableCell(node, children, parents, markdownStyles, true),
};
}, [session, viewportWidth]);
const detailText = memo?.contentMarkdown || memo?.contentText || "没有正文内容";
const searchMatches = useMemo(() => getTextSearchMatches(detailText, searchQuery), [detailText, searchQuery]);
const searchMatchLabel = searchQuery.trim() ? `${searchMatches.length > 0 ? activeMatchIndex + 1 : 0}/${searchMatches.length}` : "0/0";
@@ -5905,6 +5964,43 @@ const detailMarkdownStyles = StyleSheet.create({
strong: {
fontWeight: "800",
},
table: {
backgroundColor: "#ffffff",
borderColor: "#d8d8d8",
borderRadius: 2,
borderWidth: 1,
overflow: "hidden",
},
tableCellDivider: {
borderRightColor: "#dedede",
borderRightWidth: 1,
},
tableScroll: {
marginBottom: 10,
marginTop: 10,
maxWidth: "100%",
},
tableScrollContent: {
flexGrow: 1,
},
td: {
flex: 1,
minWidth: 0,
paddingHorizontal: 10,
paddingVertical: 7,
},
th: {
backgroundColor: "#f2f2f2",
flex: 1,
minWidth: 0,
paddingHorizontal: 10,
paddingVertical: 7,
},
tr: {
borderBottomColor: "#dedede",
borderBottomWidth: 1,
flexDirection: "row",
},
});
const baseWorkspaceStyles = StyleSheet.create({
@@ -7522,7 +7618,7 @@ const baseWorkspaceStyles = StyleSheet.create({
},
detailContent: {
paddingBottom: 112,
paddingHorizontal: 16,
paddingHorizontal: DETAIL_CONTENT_HORIZONTAL_PADDING,
paddingTop: 16,
},
detailTitle: {