feat(mobile): add web clipping and revision safeguards

This commit is contained in:
tianma-if committed 2026-07-31 11:02:17 +08:00
1 parent 956862aa89
commit b7aa004686
11 files changed
+809 -43

No files matched your search

+22
View File
@@ -58,6 +58,28 @@
"enableShrinkResourcesInReleaseBuilds": true
}
}
],
[
"expo-sharing",
{
"ios": {
"enabled": true,
"activationRule": {
"supportsText": true,
"supportsWebPageWithMaxCount": 1,
"supportsWebUrlWithMaxCount": 1
}
},
"android": {
"enabled": true,
"multipleShareMimeTypes": [
"text/plain"
],
"singleShareMimeTypes": [
"text/plain"
]
}
}
]
],
"experiments": {
+7
View File
@@ -0,0 +1,7 @@
export const redirectSystemPath = ({ path }: { path: string; initial: boolean }) => {
try {
return new URL(path).hostname === "expo-sharing" ? "/" : path;
} catch {
return "/";
}
};
+6 -1
View File
@@ -1,4 +1,5 @@
import * as SplashScreen from "expo-splash-screen";
import { useIncomingShare } from "expo-sharing";
import { useIsRestoring } from "@tanstack/react-query";
import { lazy, Suspense, useEffect } from "react";
import { ActivityIndicator, StyleSheet, View } from "react-native";
@@ -15,6 +16,7 @@ const WorkspaceScreen = lazy(() =>
export default function IndexScreen() {
const { isLoading, session } = useSession();
const isRestoringCache = useIsRestoring();
const { clearSharedPayloads, sharedPayloads } = useIncomingShare();
useEffect(() => {
if (!isLoading && !isRestoringCache) {
@@ -29,7 +31,10 @@ export default function IndexScreen() {
return session ? (
<Suspense fallback={<StartupPlaceholder showBrand />}>
<WorkspaceScreen />
<WorkspaceScreen
incomingSharePayloads={sharedPayloads}
onIncomingShareHandled={clearSharedPayloads}
/>
</Suspense>
) : (
<LoginScreen />
+1
View File
@@ -35,6 +35,7 @@
"expo-image-manipulator": "~57.0.5",
"expo-router": "latest",
"expo-secure-store": "latest",
"expo-sharing": "~57.0.8",
"expo-splash-screen": "^57.0.4",
"expo-sqlite": "~57.0.1",
"expo-status-bar": "latest",
+9
View File
@@ -31,6 +31,15 @@ const mobileOnlyTranslations = new Map<string, string>([
["输入关键词开始搜索", "Enter a keyword to search"],
["搜索本机同步缓存,结果会即时显示", "Search the local synced cache with instant results"],
["笔记操作", "Note actions"],
["版本历史", "Version history"],
["同步冲突", "Sync conflict"],
["同步失败", "Sync failed"],
["待同步", "Pending sync"],
["已同步", "Synced"],
["查看并处理同步冲突", "Review and resolve the sync conflict"],
["云端笔记已在其他设备更新,本地修改没有覆盖云端。你可以查看版本历史,或放弃本地修改并使用云端版本。", "The cloud note was updated on another device. Your local changes did not overwrite it. View version history, or discard the local changes and use the cloud version."],
["查看历史", "View history"],
["使用云端版本", "Use cloud version"],
["加载失败", "Failed to load"],
["请稍后重试", "Please try again later"],
["重试", "Retry"],
@@ -0,0 +1,64 @@
import { describe, expect, test } from "bun:test";
import {
buildMobileWebClipDraft,
extractPageTitle,
getSharedWebUrl,
htmlToMarkdown,
isWeChatArticleUrl,
} from "./mobile-web-clip";
describe("mobile web clip", () => {
test("extracts a WeChat URL embedded in shared text", () => {
expect(getSharedWebUrl([
{ shareType: "text", value: "一篇文章\nhttps://mp.weixin.qq.com/s/abc123" },
])).toBe("https://mp.weixin.qq.com/s/abc123");
expect(isWeChatArticleUrl("https://mp.weixin.qq.com/s/abc123")).toBe(true);
expect(isWeChatArticleUrl("http://mp.weixin.qq.com/s/abc123")).toBe(false);
});
test("extracts title regardless of meta attribute order", () => {
expect(extractPageTitle('<meta content="公众号文章标题 &amp; 更多" property="og:title">'))
.toBe("公众号文章标题 & 更多");
});
test("turns common article HTML into readable markdown", () => {
const markdown = htmlToMarkdown(`
<section>
<h2>第一节</h2>
<p>正文<strong>重点</strong><br>下一行</p>
<img data-src="https://mmbiz.qpic.cn/example.jpg" alt="示例图">
<p><a href="https://example.com">阅读更多</a></p>
</section>
`);
expect(markdown).toContain("## 第一节");
expect(markdown).toContain("正文**重点**\n下一行");
expect(markdown).toContain("![示例图](https://mmbiz.qpic.cn/example.jpg)");
expect(markdown).toContain("[阅读更多](https://example.com)");
});
test("builds a complete WeChat clip from nested js_content", async () => {
const html = `
<html><head><meta property="og:title" content="测试文章"></head>
<body><div id="js_content"><section><p>开头</p><div><p>嵌套正文</p></div></section></div></body></html>
`;
const draft = await buildMobileWebClipDraft("https://mp.weixin.qq.com/s/test", {
capturedAt: new Date("2026-07-31T00:00:00.000Z"),
fetcher: async () => new Response(html, { status: 200 }),
});
expect(draft.title).toBe("测试文章");
expect(draft.tagsText).toBe("web-clip, wechat");
expect(draft.contentMarkdown).toContain("开头");
expect(draft.contentMarkdown).toContain("嵌套正文");
expect(draft.contentMarkdown).toContain("2026-07-31T00:00:00.000Z");
});
test("keeps the source URL when fetching fails", async () => {
const draft = await buildMobileWebClipDraft("https://mp.weixin.qq.com/s/offline", {
fetcher: async () => {
throw new Error("offline");
},
});
expect(draft.contentMarkdown).toContain("https://mp.weixin.qq.com/s/offline");
expect(draft.contentMarkdown).toContain("正文暂时无法抓取");
});
});
+292
View File
@@ -0,0 +1,292 @@
export type MobileSharedPayload = {
mimeType?: string;
shareType?: string;
value?: string;
};
export type MobileWebClipDraft = {
contentMarkdown: string;
sourceUrl: string;
tagsText: string;
title: string;
};
const URL_PATTERN = /https?:\/\/[^\s<>"'))]+/i;
const WECHAT_ARTICLE_HOSTS = new Set(["mp.weixin.qq.com"]);
const BLOCK_TAGS = new Set([
"article",
"aside",
"blockquote",
"div",
"figure",
"figcaption",
"footer",
"header",
"main",
"p",
"section",
"table",
"tbody",
"td",
"th",
"thead",
"tr",
]);
const HTML_ENTITIES: Record<string, string> = {
amp: "&",
apos: "'",
gt: ">",
hellip: "…",
ldquo: "“",
lsquo: "‘",
lt: "<",
middot: "·",
nbsp: " ",
ndash: "–",
quot: "\"",
rdquo: "”",
rsquo: "’",
};
export const getSharedWebUrl = (payloads: MobileSharedPayload[]) => {
for (const payload of payloads) {
const value = payload.value?.trim() ?? "";
const match = value.match(URL_PATTERN);
if (!match) {
continue;
}
try {
const url = new URL(match[0]);
if (url.protocol === "http:" || url.protocol === "https:") {
return url.toString();
}
} catch {
// Ignore malformed text and continue looking through other payloads.
}
}
return null;
};
export const isWeChatArticleUrl = (value: string) => {
try {
const url = new URL(value);
return url.protocol === "https:" && WECHAT_ARTICLE_HOSTS.has(url.hostname.toLowerCase()) && url.pathname.startsWith("/s");
} catch {
return false;
}
};
export const buildMobileWebClipDraft = async (
sourceUrl: string,
options: {
fetcher?: typeof fetch;
capturedAt?: Date;
} = {}
): Promise<MobileWebClipDraft> => {
const fetcher = options.fetcher ?? fetch;
const capturedAt = options.capturedAt ?? new Date();
let html = "";
try {
const response = await fetcher(sourceUrl, {
headers: {
Accept: "text/html,application/xhtml+xml",
},
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
html = await response.text();
} catch {
return buildFallbackDraft(sourceUrl, capturedAt);
}
const title = extractPageTitle(html) || hostnameTitle(sourceUrl);
const articleHtml = isWeChatArticleUrl(sourceUrl)
? extractElementInnerHtmlById(html, "js_content")
: extractFirstElementInnerHtml(html, ["article", "main"]);
const body = htmlToMarkdown(articleHtml || "");
if (!body) {
return {
...buildFallbackDraft(sourceUrl, capturedAt),
title,
};
}
return {
title,
sourceUrl,
tagsText: isWeChatArticleUrl(sourceUrl) ? "web-clip, wechat" : "web-clip",
contentMarkdown: [
`来源:[${escapeMarkdownText(sourceUrl)}](${sourceUrl})`,
`剪藏时间:${capturedAt.toISOString()}`,
"---",
body,
].join("\n\n"),
};
};
export const extractPageTitle = (html: string) => {
for (const property of ["og:title", "twitter:title"]) {
const meta = findMetaContent(html, property);
if (meta) {
return normalizeInlineText(meta);
}
}
const activityTitle = extractElementInnerHtmlById(html, "activity-name");
if (activityTitle) {
return normalizeInlineText(stripTags(activityTitle));
}
const titleMatch = html.match(/<title\b[^>]*>([\s\S]*?)<\/title\s*>/i);
return titleMatch ? normalizeInlineText(stripTags(titleMatch[1])) : "";
};
export const htmlToMarkdown = (html: string) => {
if (!html.trim()) {
return "";
}
let value = html
.replace(/<!--[\s\S]*?-->/g, "")
.replace(/<(script|style|noscript|template|iframe|svg|canvas)\b[^>]*>[\s\S]*?<\/\1\s*>/gi, "");
value = value.replace(/<img\b[^>]*>/gi, (tag) => {
const src = readAttribute(tag, "data-src") || readAttribute(tag, "data-original") || readAttribute(tag, "src");
if (!src || src.startsWith("data:")) {
return "";
}
const alt = normalizeInlineText(readAttribute(tag, "alt") || "图片");
return `\n\n![${escapeMarkdownText(alt)}](${src})\n\n`;
});
value = value.replace(/<a\b[^>]*>([\s\S]*?)<\/a\s*>/gi, (full, inner: string) => {
const href = readAttribute(full, "href");
const label = normalizeInlineText(stripTags(inner));
if (!href || /^javascript:/i.test(href)) {
return label;
}
return `[${escapeMarkdownText(label || href)}](${href})`;
});
value = value
.replace(/<h([1-6])\b[^>]*>/gi, (_tag, level: string) => `\n\n${"#".repeat(Number(level))} `)
.replace(/<\/h[1-6]\s*>/gi, "\n\n")
.replace(/<br\s*\/?>/gi, "\n")
.replace(/<hr\b[^>]*>/gi, "\n\n---\n\n")
.replace(/<li\b[^>]*>/gi, "\n- ")
.replace(/<\/li\s*>/gi, "\n")
.replace(/<(strong|b)\b[^>]*>/gi, "**")
.replace(/<\/(strong|b)\s*>/gi, "**")
.replace(/<(em|i)\b[^>]*>/gi, "*")
.replace(/<\/(em|i)\s*>/gi, "*")
.replace(/<code\b[^>]*>/gi, "`")
.replace(/<\/code\s*>/gi, "`")
.replace(/<\/?blockquote\b[^>]*>/gi, "\n\n> ")
.replace(/<\/?([a-z][a-z0-9-]*)\b[^>]*>/gi, (tag, name: string) =>
BLOCK_TAGS.has(name.toLowerCase()) ? "\n\n" : tag.startsWith("</") ? "" : ""
);
return decodeHtmlEntities(value)
.replace(/\r/g, "")
.replace(/[ \t]+\n/g, "\n")
.replace(/\n[ \t]+/g, "\n")
.replace(/[ \t]{2,}/g, " ")
.replace(/\n{3,}/g, "\n\n")
.trim();
};
const buildFallbackDraft = (sourceUrl: string, capturedAt: Date): MobileWebClipDraft => ({
title: hostnameTitle(sourceUrl),
sourceUrl,
tagsText: isWeChatArticleUrl(sourceUrl) ? "web-clip, wechat" : "web-clip",
contentMarkdown: [
`来源:[${escapeMarkdownText(sourceUrl)}](${sourceUrl})`,
`剪藏时间:${capturedAt.toISOString()}`,
"",
"正文暂时无法抓取,来源链接已保留,可稍后重试。",
].join("\n\n"),
});
const hostnameTitle = (value: string) => {
try {
return new URL(value).hostname;
} catch {
return "网页剪藏";
}
};
const findMetaContent = (html: string, property: string) => {
const tags = html.match(/<meta\b[^>]*>/gi) ?? [];
for (const tag of tags) {
const key = readAttribute(tag, "property") || readAttribute(tag, "name");
if (key.toLowerCase() === property.toLowerCase()) {
return decodeHtmlEntities(readAttribute(tag, "content"));
}
}
return "";
};
const extractFirstElementInnerHtml = (html: string, tags: string[]) => {
for (const tag of tags) {
const match = html.match(new RegExp(`<${tag}\\b[^>]*>([\\s\\S]*?)<\\/${tag}\\s*>`, "i"));
if (match?.[1]) {
return match[1];
}
}
return "";
};
const extractElementInnerHtmlById = (html: string, id: string) => {
const openingTagPattern = /<([a-z][a-z0-9-]*)\b[^>]*>/gi;
let openingMatch: RegExpExecArray | null;
while ((openingMatch = openingTagPattern.exec(html))) {
if (readAttribute(openingMatch[0], "id") !== id) {
continue;
}
const tagName = openingMatch[1].toLowerCase();
const contentStart = openingTagPattern.lastIndex;
const boundaryPattern = new RegExp(`<\\/?${tagName}\\b[^>]*>`, "gi");
boundaryPattern.lastIndex = contentStart;
let depth = 1;
let boundaryMatch: RegExpExecArray | null;
while ((boundaryMatch = boundaryPattern.exec(html))) {
if (boundaryMatch[0].startsWith("</")) {
depth -= 1;
if (depth === 0) {
return html.slice(contentStart, boundaryMatch.index);
}
} else if (!boundaryMatch[0].endsWith("/>")) {
depth += 1;
}
}
return html.slice(contentStart);
}
return "";
};
const readAttribute = (tag: string, name: string) => {
const escapedName = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
const match = tag.match(new RegExp(`(?:^|\\s)${escapedName}\\s*=\\s*(?:"([^"]*)"|'([^']*)'|([^\\s>]+))`, "i"));
return decodeHtmlEntities(match?.[1] ?? match?.[2] ?? match?.[3] ?? "");
};
const stripTags = (value: string) => value.replace(/<[^>]*>/g, " ");
const normalizeInlineText = (value: string) => decodeHtmlEntities(value).replace(/\s+/g, " ").trim();
const escapeMarkdownText = (value: string) => value.replace(/([\\[\]])/g, "\\$1");
const decodeHtmlEntities = (value: string) =>
value.replace(/&(#x?[0-9a-f]+|[a-z][a-z0-9]+);?/gi, (entity, key: string) => {
if (key.startsWith("#x") || key.startsWith("#X")) {
const codePoint = Number.parseInt(key.slice(2), 16);
return Number.isFinite(codePoint) ? String.fromCodePoint(codePoint) : entity;
}
if (key.startsWith("#")) {
const codePoint = Number.parseInt(key.slice(1), 10);
return Number.isFinite(codePoint) ? String.fromCodePoint(codePoint) : entity;
}
return HTML_ENTITIES[key.toLowerCase()] ?? entity;
});
+24
View File
@@ -1,4 +1,5 @@
import { beforeEach, expect, mock, test } from "bun:test";
import { ApiRequestError } from "@edgeever/client";
import type { MemoDetail } from "@edgeever/shared";
const storage = new Map<string, string>();
@@ -39,6 +40,29 @@ test("keeps pending updates isolated by instance", async () => {
expect((await listMobileSyncQueueItems("https://two.example"))[0]?.payload.title).toBe("Second instance");
});
test("keeps a stale mobile edit as an explicit conflict without overwriting the cloud note", async () => {
const scope = "https://one.example";
await queueMobileMemoUpdate(scope, basePayload);
let updateCalled = false;
const client = {
createMemoEditSession: async () => ({
editSession: { id: "edit-2", baseRevision: 2, baseContentHash: "hash-2" },
}),
updateMemo: async () => {
updateCalled = true;
throw new ApiRequestError("Unexpected update", 500, "unexpected_update");
},
};
const result = await syncMobileQueuedChanges(client as never, scope);
const queued = (await listMobileSyncQueueItems(scope))[0];
expect(result.conflicted).toBe(1);
expect(updateCalled).toBe(false);
expect(queued?.status).toBe("conflict");
expect(queued?.payload.contentMarkdown).toBe("first");
});
test("rebases a newer local save when an older save finishes syncing", async () => {
await queueMobileMemoUpdate("https://one.example", basePayload);
+279 -16
View File
@@ -83,12 +83,14 @@ import {
import { useMobileLocale } from "../lib/mobile-locale";
import { useSession } from "../lib/session";
import {
deleteMobileSyncQueueItem,
getMobileSyncRetryDelay,
listMobileSyncQueueItems,
loadMobileSyncQueueSummary,
queueMobileMemoCreate,
queueMobileMemoUpdate,
syncMobileQueuedChanges,
type MobileSyncQueueItem,
} from "../lib/sync-queue";
import {
createMobileDataScope,
@@ -112,6 +114,12 @@ import { resolveMobileThemeStyles, useMobileTheme, type MobileResolvedTheme } fr
import { useMobileUpdate } from "../lib/mobile-update";
import { MobileMermaidDiagram, MobileMermaidProvider } from "../components/MobileMermaid";
import { getMobileMarkdownFenceLanguage, trimMobileMarkdownFenceContent } from "../lib/mobile-mermaid";
import {
buildMobileWebClipDraft,
getSharedWebUrl,
type MobileSharedPayload,
type MobileWebClipDraft,
} from "../lib/mobile-web-clip";
const ALL_NOTES_ID = "all";
const DEFAULT_MEMO_TITLE = "无标题笔记";
@@ -167,7 +175,13 @@ type RichEditingSession = {
};
type MobileMemoUpdateMutation = UseMutationResult<MemoDetail, Error, { memo: MemoDetail; payload: MobileMemoUpdatePayload }>;
export const WorkspaceScreen = () => {
export const WorkspaceScreen = ({
incomingSharePayloads = [],
onIncomingShareHandled,
}: {
incomingSharePayloads?: MobileSharedPayload[];
onIncomingShareHandled?: () => void;
}) => {
const { resolvedTheme } = useMobileTheme();
const { preference: localePreference, setPreference: setLocalePreference } = useMobileLocale();
refreshWorkspaceThemeStyles(resolvedTheme);
@@ -187,6 +201,8 @@ export const WorkspaceScreen = () => {
const [selectedMemoId, setSelectedMemoId] = useState<string | null>(null);
const [searchText, setSearchText] = useState("");
const [createOpen, setCreateOpen] = useState(false);
const [incomingClipDraft, setIncomingClipDraft] = useState<MobileWebClipDraft | null>(null);
const [isImportingShare, setIsImportingShare] = useState(false);
const [notesActionsOpen, setNotesActionsOpen] = useState(false);
const [notebookPickerOpen, setNotebookPickerOpen] = useState(false);
const [richEditingSession, setRichEditingSession] = useState<RichEditingSession | null>(null);
@@ -204,7 +220,18 @@ export const WorkspaceScreen = () => {
const autoSyncRequestedRef = useRef(false);
const autoSyncRetryTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const memoDraftPrefetchRef = useRef(new Map<string, Promise<MobileMemoDraft | null>>());
const processedShareUrlRef = useRef<string | null>(null);
const onIncomingShareHandledRef = useRef(onIncomingShareHandled);
onIncomingShareHandledRef.current = onIncomingShareHandled;
const [syncQueueItems, setSyncQueueItems] = useState<MobileSyncQueueItem[]>([]);
const debouncedSearchText = useDebouncedValue(searchText.trim(), 250);
const incomingShareUrl = useMemo(() => getSharedWebUrl(incomingSharePayloads), [incomingSharePayloads]);
const refreshSyncQueueItems = useCallback(async () => {
const items = await listMobileSyncQueueItems(syncQueueScope);
setSyncQueueItems(items);
return items;
}, [syncQueueScope]);
const notebooksQuery = useQuery({
queryKey: ["mobile", "notebooks"],
@@ -489,16 +516,34 @@ export const WorkspaceScreen = () => {
const openRichEditor = useCallback(async (memo: MemoDetail) => {
beginEditorStartup();
const draft = await loadMemoDraft(memo.id);
let editingMemo = memo;
const queuedItem = (await listMobileSyncQueueItems(syncQueueScope)).find((item) => item.memoId === memo.id);
if (!queuedItem && client && !memo.id.startsWith("local:")) {
try {
const response = await client.getMemo(memo.id);
editingMemo = response.memo;
await upsertLocalMemo(dataScope, editingMemo);
queryClient.setQueryData(["mobile", "memo", "notebook", editingMemo.id], { memo: editingMemo });
queryClient.setQueryData(["mobile", "memo", "trash", editingMemo.id], { memo: editingMemo });
} catch {
// The local mirror remains editable while offline.
}
}
const draft = await loadMemoDraft(editingMemo.id);
memoDraftPrefetchRef.current.delete(memo.id);
setRichEditingSession({ draft, memo });
}, [loadMemoDraft]);
setRichEditingSession({ draft, memo: editingMemo });
}, [client, dataScope, loadMemoDraft, queryClient, syncQueueScope]);
const memos = useMemo(() => memosQuery.data?.pages.flatMap((page) => page.memos) ?? [], [memosQuery.data]);
const searchResults = useMemo(() => searchQuery.data?.pages.flatMap((page) => page.memos) ?? [], [searchQuery.data]);
const searchActive = searchText.trim().length > 0;
const visibleMemos = searchActive ? searchResults : memos;
const selectedMemo = memoDetailQuery.data?.memo ?? null;
const selectedMemoSyncStatus = selectedMemo
? syncQueueItems.find((item) => item.memoId === selectedMemo.id)?.status ?? null
: null;
const isRefreshing = notebooksQuery.isFetching || memosQuery.isFetching || searchQuery.isFetching || memoDetailQuery.isFetching;
const selectedMemoIdList = Array.from(selectedMemoIds);
const selectedMemos = visibleMemos.filter((memo) => selectedMemoIds.has(memo.id));
@@ -515,9 +560,65 @@ export const WorkspaceScreen = () => {
const canCreateMemo = memoView !== "trash" && Boolean(createMemoNotebookId);
const openCreateMemo = () => {
beginEditorStartup();
setIncomingClipDraft(null);
setCreateOpen(true);
};
useEffect(() => {
const sourceUrl = incomingShareUrl;
if (!sourceUrl) {
if (incomingSharePayloads.length > 0 && processedShareUrlRef.current !== "invalid-share") {
processedShareUrlRef.current = "invalid-share";
Alert.alert("无法剪藏", "分享内容里没有可识别的网页链接。");
onIncomingShareHandledRef.current?.();
return;
}
processedShareUrlRef.current = null;
return;
}
if (processedShareUrlRef.current === sourceUrl) {
return;
}
if (notebooks.length === 0) {
if (notebooksQuery.isSuccess) {
processedShareUrlRef.current = sourceUrl;
Alert.alert("无法保存剪藏", "请先在 EdgeEver 中创建一个笔记本。");
onIncomingShareHandledRef.current?.();
}
return;
}
let active = true;
processedShareUrlRef.current = sourceUrl;
setIsImportingShare(true);
void buildMobileWebClipDraft(sourceUrl)
.then((draft) => {
if (!active) {
return;
}
beginEditorStartup();
setIncomingClipDraft(draft);
setActiveView("notes");
setMemoView("notebook");
setCreateOpen(true);
})
.catch(() => {
if (active) {
Alert.alert("剪藏失败", "无法读取分享的网页,请稍后重试。");
}
})
.finally(() => {
if (active) {
setIsImportingShare(false);
onIncomingShareHandledRef.current?.();
}
});
return () => {
active = false;
};
}, [incomingSharePayloads.length, incomingShareUrl, notebooks.length, notebooksQuery.isSuccess]);
useEffect(() => {
if (selectedMemo && !selectedMemo.isDeleted) {
void loadMemoDraft(selectedMemo.id);
@@ -531,7 +632,7 @@ export const WorkspaceScreen = () => {
useEffect(() => {
let mounted = true;
listMobileSyncQueueItems(syncQueueScope).then((items) => {
refreshSyncQueueItems().then((items) => {
if (!mounted) {
return;
}
@@ -548,7 +649,7 @@ export const WorkspaceScreen = () => {
return () => {
mounted = false;
};
}, [queryClient, syncQueueScope]);
}, [queryClient, refreshSyncQueueItems]);
useEffect(() => {
let mounted = true;
@@ -646,6 +747,7 @@ export const WorkspaceScreen = () => {
notebookId: optimisticMemo.notebookId,
tags: optimisticMemo.tags,
});
await refreshSyncQueueItems();
return optimisticMemo;
},
@@ -751,6 +853,50 @@ export const WorkspaceScreen = () => {
]);
};
const handleMemoSyncConflict = (memo: MemoDetail) => {
Alert.alert(
"同步冲突",
"云端笔记已在其他设备更新,本地修改没有覆盖云端。你可以查看版本历史,或放弃本地修改并使用云端版本。",
[
{ text: "取消", style: "cancel" },
{ text: "查看历史", onPress: () => setRevisionMemo(memo) },
{
text: "使用云端版本",
style: "destructive",
onPress: () => {
void (async () => {
if (!client) {
return;
}
try {
const conflict = (await listMobileSyncQueueItems(syncQueueScope))
.find((item) => item.memoId === memo.id && item.status === "conflict");
const response = await client.getMemo(memo.id);
if (conflict) {
await deleteMobileSyncQueueItem(syncQueueScope, conflict.id);
}
await Promise.all([
clearMobileMemoDraft(memo.id),
upsertLocalMemo(dataScope, response.memo),
]);
queryClient.setQueryData(["mobile", "memo", "notebook", memo.id], response);
queryClient.setQueryData(["mobile", "memo", "trash", memo.id], response);
await Promise.all([
refreshSyncQueueItems(),
queryClient.invalidateQueries({ queryKey: ["mobile", "memos"] }),
queryClient.invalidateQueries({ queryKey: ["mobile", "search"] }),
]);
} catch (error) {
Alert.alert("加载失败", error instanceof Error ? error.message : "请稍后重试");
}
})();
},
},
]
);
};
const handleDeleteSelection = () => {
const permanent = memoView === "trash";
if (!permanent) {
@@ -792,6 +938,7 @@ export const WorkspaceScreen = () => {
const summary = await loadMobileSyncQueueSummary(syncQueueScope);
if (summary.pending + summary.error + summary.syncing === 0) {
await refreshSyncQueueItems();
autoSyncRunningRef.current = false;
return;
}
@@ -816,6 +963,7 @@ export const WorkspaceScreen = () => {
} catch {
// The queue keeps its retry metadata; the next scheduled pass resumes it.
} finally {
await refreshSyncQueueItems();
autoSyncRunningRef.current = false;
if (autoSyncRequestedRef.current) {
autoSyncRequestedRef.current = false;
@@ -992,14 +1140,16 @@ export const WorkspaceScreen = () => {
isDeleting={deleteMemoMutation.isPending}
isLoading={memoDetailQuery.isLoading}
isRestoring={restoreMemoMutation.isPending}
isSaving={updateMemoMutation.isPending}
isSaving={updateMemoMutation.isPending || localUpdateMemoMutation.isPending}
memo={selectedMemo}
notebookName={notebooks.find((notebook) => notebook.id === selectedMemo?.notebookId)?.name ?? "未分类"}
onClose={closeDetail}
onDelete={handleDeleteMemo}
onRichEdit={(memo) => void openRichEditor(memo)}
onOpenRevisions={setRevisionMemo}
onResolveSyncConflict={handleMemoSyncConflict}
onRestore={(memo) => restoreMemoMutation.mutate(memo)}
syncStatus={selectedMemoSyncStatus}
visible={Boolean(selectedMemoId)}
/>
@@ -1027,7 +1177,17 @@ export const WorkspaceScreen = () => {
{revisionMemo ? <RevisionHistoryModal
memo={revisionMemo}
onClose={() => setRevisionMemo(null)}
onRestored={(memo) => {
onRestored={async (memo) => {
const queuedItems = (await listMobileSyncQueueItems(syncQueueScope))
.filter((item) => item.memoId === memo.id);
await Promise.all([
...queuedItems.map((item) => deleteMobileSyncQueueItem(syncQueueScope, item.id)),
clearMobileMemoDraft(memo.id),
upsertLocalMemo(dataScope, memo),
]);
queryClient.setQueryData(["mobile", "memo", "notebook", memo.id], { memo });
queryClient.setQueryData(["mobile", "memo", "trash", memo.id], { memo });
await refreshSyncQueueItems();
setRevisionMemo(null);
setSelectedMemoId(memo.id);
}}
@@ -1038,9 +1198,11 @@ export const WorkspaceScreen = () => {
dataScope={dataScope}
defaultNotebookId={createMemoNotebookId}
imageCompressionEnabled={imageCompressionEnabled}
initialDraft={incomingClipDraft}
notebooks={notebooks}
onCreated={() => {
setCreateOpen(false);
setIncomingClipDraft(null);
setActiveView("notes");
setMemoView("notebook");
setSelectedMemoId(null);
@@ -1050,6 +1212,16 @@ export const WorkspaceScreen = () => {
visible={createOpen}
/>
<Modal animationType="fade" statusBarTranslucent transparent visible={isImportingShare}>
<View style={styles.shareImportBackdrop}>
<View style={styles.shareImportCard}>
<ActivityIndicator color="#059669" size="large" />
<Text style={styles.shareImportTitle}>正在剪藏文章</Text>
<Text style={styles.shareImportDescription}>正在提取标题、正文和图片链接…</Text>
</View>
</View>
</Modal>
{selectionMoveOpen ? <MoveSelectionModal
bottomOffset={58 + safeAreaInsets.bottom}
isMoving={moveMemosMutation.isPending}
@@ -1842,6 +2014,7 @@ const CreateMemoModal = ({
dataScope,
defaultNotebookId,
imageCompressionEnabled,
initialDraft,
notebooks,
onCreated,
onQueued,
@@ -1852,6 +2025,7 @@ const CreateMemoModal = ({
dataScope: string;
defaultNotebookId: string;
imageCompressionEnabled: boolean;
initialDraft?: MobileWebClipDraft | null;
notebooks: Notebook[];
onCreated: (memo: MemoDetail) => void;
onQueued: () => void | Promise<void>;
@@ -1895,6 +2069,21 @@ const CreateMemoModal = ({
let active = true;
setDraftLoaded(false);
setEditorReady(false);
if (initialDraft) {
const markdown = initialDraft.contentMarkdown;
contentMarkdownRef.current = markdown;
contentJsonRef.current = markdownToDoc(markdown);
draftVersionRef.current = 0;
setTitle(initialDraft.title);
setTagsText(initialDraft.tagsText);
setContentMarkdown(markdown);
setNotebookId(fallbackNotebookId);
setDirty(false);
setDraftLoaded(true);
return () => {
active = false;
};
}
void readMobileNewMemoDraft(dataScope).then((draft) => {
if (!active) {
return;
@@ -1916,10 +2105,10 @@ const CreateMemoModal = ({
return () => {
active = false;
};
}, [fallbackNotebookId, visible]);
}, [dataScope, fallbackNotebookId, initialDraft, visible]);
useEffect(() => {
if (!visible || !draftLoaded || !dirty) {
if (!visible || !draftLoaded || !dirty || initialDraft) {
return;
}
const draftVersion = draftVersionRef.current;
@@ -1949,7 +2138,7 @@ const CreateMemoModal = ({
});
}, 350);
return () => clearTimeout(timeout);
}, [dataScope, dirty, draftLoaded, tagsText, targetNotebookId, title, visible, contentMarkdown]);
}, [dataScope, dirty, draftLoaded, initialDraft, tagsText, targetNotebookId, title, visible, contentMarkdown]);
const createMutation = useMutation({
mutationFn: async () => {
@@ -2028,7 +2217,9 @@ const CreateMemoModal = ({
materializedMemoRef.current = null;
draftVersionRef.current += 1;
setDirty(false);
await clearMobileNewMemoDraft(dataScope);
if (!initialDraft) {
await clearMobileNewMemoDraft(dataScope);
}
if (materializedMemoId) {
await clearMobileMemoDraft(materializedMemoId);
}
@@ -2054,7 +2245,9 @@ const CreateMemoModal = ({
});
materializedMemoRef.current = response.memo;
await upsertLocalMemo(dataScope, response.memo);
await clearMobileNewMemoDraft(dataScope);
if (!initialDraft) {
await clearMobileNewMemoDraft(dataScope);
}
await Promise.all([
queryClient.invalidateQueries({ queryKey: ["mobile", "notebooks"] }),
queryClient.invalidateQueries({ queryKey: ["mobile", "memos"] }),
@@ -2349,7 +2542,7 @@ const RevisionHistoryModal = ({
}: {
memo: MemoDetail | null;
onClose: () => void;
onRestored: (memo: MemoDetail) => void;
onRestored: (memo: MemoDetail) => void | Promise<void>;
}) => {
const { client } = useSession();
const queryClient = useQueryClient();
@@ -2403,7 +2596,7 @@ const RevisionHistoryModal = ({
queryClient.invalidateQueries({ queryKey: ["mobile", "memo"] }),
queryClient.invalidateQueries({ queryKey: ["mobile", "memo-revisions", restoredMemo.id] }),
]);
onRestored(restoredMemo);
await onRestored(restoredMemo);
},
});
@@ -2657,7 +2850,9 @@ const MemoDetailModal = ({
onDelete,
onRichEdit,
onOpenRevisions,
onResolveSyncConflict,
onRestore,
syncStatus,
visible,
}: {
isDeleting: boolean;
@@ -2670,7 +2865,9 @@ const MemoDetailModal = ({
onDelete: (memo: MemoDetail) => void;
onRichEdit: (memo: MemoDetail) => void;
onOpenRevisions: (memo: MemoDetail) => void;
onResolveSyncConflict: (memo: MemoDetail) => void;
onRestore: (memo: MemoDetail) => void;
syncStatus: MobileSyncQueueItem["status"] | null;
visible: boolean;
}) => {
const { session } = useSession();
@@ -2770,6 +2967,15 @@ const MemoDetailModal = ({
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";
const syncStatusLabel = isSaving || syncStatus === "syncing"
? "保存中"
: syncStatus === "conflict"
? "同步冲突"
: syncStatus === "error"
? "同步失败"
: syncStatus === "pending"
? "待同步"
: "已同步";
const editFabBottom = Math.max(
safeAreaInsets.bottom,
Platform.OS === "android" ? ANDROID_SYSTEM_NAVIGATION_FALLBACK : 0
@@ -2800,7 +3006,30 @@ const MemoDetailModal = ({
<ChevronLeft color="#475569" size={21} />
</Pressable>
<View style={styles.detailHeaderActions}>
<Text numberOfLines={1} style={styles.detailSyncStatus}>{isSaving ? "保存中" : "已保存"}</Text>
<Pressable
accessibilityHint={syncStatus === "conflict" ? "查看并处理同步冲突" : undefined}
accessibilityLabel={syncStatusLabel}
accessibilityRole={syncStatus === "conflict" ? "button" : "text"}
disabled={syncStatus !== "conflict" || !memo}
onPress={() => memo && onResolveSyncConflict(memo)}
>
<Text
numberOfLines={1}
style={[styles.detailSyncStatus, syncStatus === "conflict" && styles.detailSyncStatusConflict]}
>
{syncStatusLabel}
</Text>
</Pressable>
{memo && !memo.isDeleted ? (
<Pressable
accessibilityLabel="版本历史"
accessibilityRole="button"
onPress={() => onOpenRevisions(memo)}
style={styles.detailHeaderIconButton}
>
<History color="#475569" size={20} />
</Pressable>
) : null}
{memo?.isDeleted ? (
<Pressable accessibilityLabel="笔记操作" accessibilityRole="button" onPress={() => setActionsOpen(true)} style={styles.detailHeaderIconButton}>
<MoreHorizontal color="#475569" size={21} />
@@ -6090,6 +6319,11 @@ const baseWorkspaceStyles = StyleSheet.create({
paddingHorizontal: 8,
paddingVertical: 4,
},
detailSyncStatusConflict: {
backgroundColor: "#fff7ed",
color: "#c2410c",
fontWeight: "700",
},
detailMetaRow: {
alignItems: "center",
flexDirection: "row",
@@ -6485,6 +6719,35 @@ const baseWorkspaceStyles = StyleSheet.create({
flex: 1,
justifyContent: "center",
},
shareImportBackdrop: {
alignItems: "center",
backgroundColor: "rgba(15, 23, 42, 0.42)",
flex: 1,
justifyContent: "center",
padding: 24,
},
shareImportCard: {
alignItems: "center",
backgroundColor: "#ffffff",
borderRadius: 18,
gap: 10,
maxWidth: 340,
paddingHorizontal: 28,
paddingVertical: 26,
width: "100%",
},
shareImportTitle: {
color: "#0f172a",
fontSize: 17,
fontWeight: "800",
marginTop: 4,
},
shareImportDescription: {
color: "#64748b",
fontSize: 13,
lineHeight: 20,
textAlign: "center",
},
});
let styles = baseWorkspaceStyles;
+97 -26
View File
@@ -55,7 +55,7 @@
"@electron/asar": "3.4.1",
"@types/marked": "^6.0.0",
"@types/node": "latest",
"@types/react": "latest",
"@types/react": "^19.2.18",
"@types/react-dom": "latest",
"@types/turndown": "^5.0.6",
"@vitejs/plugin-react": "latest",
@@ -123,6 +123,7 @@
"expo-image-manipulator": "~57.0.5",
"expo-router": "latest",
"expo-secure-store": "latest",
"expo-sharing": "~57.0.8",
"expo-splash-screen": "^57.0.4",
"expo-sqlite": "~57.0.1",
"expo-status-bar": "latest",
@@ -723,7 +724,7 @@
"@expo/sudo-prompt": ["@expo/sudo-prompt@9.3.2", "", {}, "sha512-HHQigo3rQWKMDzYDLkubN5WQOYXJJE2eNqIQC2axC2iO3mHdwnIR7FgZVvHWtBwAdzBgAP0ECp8KqS8TiMKvgw=="],
"@expo/ui": ["@expo/ui@57.0.7", "", { "dependencies": { "sf-symbols-typescript": "^2.1.0", "vaul": "^1.1.2" }, "peerDependencies": { "@babel/core": "*", "expo": "*", "react": "*", "react-dom": "*", "react-native": "*", "react-native-worklets": "*" }, "optionalPeers": ["@babel/core", "react-dom", "react-native-worklets"] }, "sha512-WqRVabl8VpHf3+YLHVjUy7PMIuXXI6DG88Vgmavro7Nd8Ks13h9sEJH/RLSCaJE2daVnqzEKY1v797BibuY9aw=="],
"@expo/ui": ["@expo/ui@57.0.8", "", { "dependencies": { "sf-symbols-typescript": "^2.1.0", "vaul": "^1.1.2" }, "peerDependencies": { "@babel/core": "*", "expo": "*", "react": "*", "react-dom": "*", "react-native": "*", "react-native-worklets": "*" }, "optionalPeers": ["@babel/core", "react-dom", "react-native-worklets"] }, "sha512-aR17CRM45k4JyFahX4Xn2b6ti3aiddC4SAzvsm3iUCVacQA6tGmClu012rYNeVXhJ96csX2o7EEymfzYqgJNxw=="],
"@expo/vector-icons": ["@expo/vector-icons@15.1.1", "", { "peerDependencies": { "expo-font": ">=14.0.4", "react": "*", "react-native": "*" } }, "sha512-Iu2VkcoI5vygbtYngm7jb4ifxElNVXQYdDrYkT7UCEIiKLeWnQY0wf2ZhHZ+Wro6Sc5TaumpKUOqDRpLi5rkvw=="],
@@ -985,15 +986,15 @@
"@react-native-masked-view/masked-view": ["@react-native-masked-view/masked-view@0.3.2", "", { "peerDependencies": { "react": ">=16", "react-native": ">=0.57" } }, "sha512-XwuQoW7/GEgWRMovOQtX3A4PrXhyaZm0lVUiY8qJDvdngjLms9Cpdck6SmGAUNqQwcj2EadHC1HwL0bEyoa/SQ=="],
"@react-native/assets-registry": ["@react-native/assets-registry@0.86.0", "", {}, "sha512-nIaXbm2jX1OTYp0qbviJ3O6KZivoE8z3BnhUQ2LsqfZSWRoOK/n1qsiAr6oALiNKWnXY3j2KPwtYORnZzp8xew=="],
"@react-native/assets-registry": ["@react-native/assets-registry@0.86.2", "", {}, "sha512-vcX/mBjWAVnWofu7KecotquI2unZ/tITwA7OGdq/mdY/zmGXIEvYhfEYyOQij/LRqi9WAL+iizInTBWnxDhK/Q=="],
"@react-native/babel-plugin-codegen": ["@react-native/babel-plugin-codegen@0.86.0", "", { "dependencies": { "@babel/traverse": "^7.29.0", "@react-native/codegen": "0.86.0" } }, "sha512-qdsABWNW7uTll90l4Vh03gjeyu3WVDi2CyiiyvYGMRDcoYbjbQi6df3BMAm9lQI2yslZ1T14LlDDAsgTwNxplA=="],
"@react-native/babel-plugin-codegen": ["@react-native/babel-plugin-codegen@0.86.2", "", { "dependencies": { "@babel/traverse": "^7.29.0", "@react-native/codegen": "0.86.2" } }, "sha512-NNDZqOlNbH5SzgPks1jFDYH3234Rpa5e/nhZymxhIiBH3NcE3uD+rGj/HWXhH7nHF2ToGK6XbUpqy7nmJPeh+g=="],
"@react-native/babel-preset": ["@react-native/babel-preset@0.86.0", "", { "dependencies": { "@babel/core": "^7.25.2", "@babel/plugin-proposal-export-default-from": "^7.24.7", "@babel/plugin-syntax-dynamic-import": "^7.8.3", "@babel/plugin-syntax-export-default-from": "^7.24.7", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", "@babel/plugin-syntax-optional-chaining": "^7.8.3", "@babel/plugin-transform-async-generator-functions": "^7.25.4", "@babel/plugin-transform-async-to-generator": "^7.24.7", "@babel/plugin-transform-block-scoping": "^7.25.0", "@babel/plugin-transform-class-properties": "^7.25.4", "@babel/plugin-transform-classes": "^7.25.4", "@babel/plugin-transform-destructuring": "^7.24.8", "@babel/plugin-transform-flow-strip-types": "^7.25.2", "@babel/plugin-transform-for-of": "^7.24.7", "@babel/plugin-transform-modules-commonjs": "^7.24.8", "@babel/plugin-transform-named-capturing-groups-regex": "^7.24.7", "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.7", "@babel/plugin-transform-optional-catch-binding": "^7.24.7", "@babel/plugin-transform-optional-chaining": "^7.24.8", "@babel/plugin-transform-private-methods": "^7.24.7", "@babel/plugin-transform-private-property-in-object": "^7.24.7", "@babel/plugin-transform-react-display-name": "^7.24.7", "@babel/plugin-transform-react-jsx": "^7.25.2", "@babel/plugin-transform-react-jsx-self": "^7.24.7", "@babel/plugin-transform-react-jsx-source": "^7.24.7", "@babel/plugin-transform-regenerator": "^7.24.7", "@babel/plugin-transform-runtime": "^7.24.7", "@babel/plugin-transform-typescript": "^7.25.2", "@babel/plugin-transform-unicode-regex": "^7.24.7", "@react-native/babel-plugin-codegen": "0.86.0", "babel-plugin-syntax-hermes-parser": "0.36.0", "babel-plugin-transform-flow-enums": "^0.0.2", "react-refresh": "^0.14.0" } }, "sha512-bYQcWiPySNvF4dns9Ls9gMmwgq66ohvM9Fwc/Kn8r85t66UNHxch3p1QwPiSorDelFauZwJbgo9+ReibTgvpbA=="],
"@react-native/codegen": ["@react-native/codegen@0.86.0", "", { "dependencies": { "@babel/core": "^7.25.2", "@babel/parser": "^7.29.0", "hermes-parser": "0.36.0", "invariant": "^2.2.4", "nullthrows": "^1.1.1", "tinyglobby": "^0.2.15", "yargs": "^17.6.2" } }, "sha512-uTs9DBo3+/lUqinsGZK0FKJRBVClrwMXoZToaDxE1Q2SL2e55vs2GwyZfIKzPl5uJnbu4PfFMIp0/mLXLWUMuA=="],
"@react-native/codegen": ["@react-native/codegen@0.86.2", "", { "dependencies": { "@babel/core": "^7.25.2", "@babel/parser": "^7.29.0", "hermes-parser": "0.36.0", "invariant": "^2.2.4", "nullthrows": "^1.1.1", "tinyglobby": "^0.2.15", "yargs": "^17.6.2" } }, "sha512-xKkudsahUJ1n//55g4fXk5BStVqqmZlz8HQveL45ZxcfDnwvhuYe2GymksQANFsSN+slvrarjrfq8kIxJzbceA=="],
"@react-native/community-cli-plugin": ["@react-native/community-cli-plugin@0.86.0", "", { "dependencies": { "@react-native/dev-middleware": "0.86.0", "debug": "^4.4.0", "invariant": "^2.2.4", "metro": "^0.84.3", "metro-config": "^0.84.3", "metro-core": "^0.84.3", "semver": "^7.1.3" }, "peerDependencies": { "@react-native-community/cli": "*", "@react-native/metro-config": "0.86.0" }, "optionalPeers": ["@react-native-community/cli", "@react-native/metro-config"] }, "sha512-Jv8p1ebEPfTzs8gmrjsdT2XMXFfeAg45Pman+XPLFGaSeGAZkutRFRyX9Cs9aGTSOyIA9YPJ6vDNb1ayTf1FKQ=="],
"@react-native/community-cli-plugin": ["@react-native/community-cli-plugin@0.86.2", "", { "dependencies": { "@react-native/dev-middleware": "0.86.2", "debug": "^4.4.0", "invariant": "^2.2.4", "metro": "^0.84.3", "metro-config": "^0.84.3", "metro-core": "^0.84.3", "semver": "^7.1.3" }, "peerDependencies": { "@react-native-community/cli": "*", "@react-native/metro-config": "0.86.2" }, "optionalPeers": ["@react-native-community/cli", "@react-native/metro-config"] }, "sha512-YHXNKoM6Y/HjREySZ5arET2xgiHgg67r1MdwJB//MPJAJ0Xc5g0u6UHxY9VzsHO3Y07dre6s0BinYwjt1SEWvQ=="],
"@react-native/debugger-frontend": ["@react-native/debugger-frontend@0.86.0", "", {}, "sha512-7Mb3nDfyJeys+ELF75Ageu7VKERlnIMoO+aNPoXqTXvz+b41L6l2CqMyLpDHxkBSlenij6gEepPNgaIyWHbJZw=="],
@@ -1001,17 +1002,17 @@
"@react-native/dev-middleware": ["@react-native/dev-middleware@0.86.0", "", { "dependencies": { "@isaacs/ttlcache": "^1.4.1", "@react-native/debugger-frontend": "0.86.0", "@react-native/debugger-shell": "0.86.0", "chrome-launcher": "^0.15.2", "chromium-edge-launcher": "^0.3.0", "connect": "^3.6.5", "debug": "^4.4.0", "invariant": "^2.2.4", "nullthrows": "^1.1.1", "open": "^7.0.3", "serve-static": "^1.16.2", "ws": "^7.5.10" } }, "sha512-20pTO6yTybmvXvro520H6C7jydIQnLKOl5qFtVEcHSdFrY63r3OGei+Rx9bILgSRmH6jgnfEcijcMx7pwWuQtw=="],
"@react-native/gradle-plugin": ["@react-native/gradle-plugin@0.86.0", "", {}, "sha512-a1RcfaEDqWExCGfCwadIxt4l8FvKYgFqeMf2uzeKyAOnb+vTGNIeCvifFL2MqvgaeYxlER437HbMIajGcuJ1pQ=="],
"@react-native/gradle-plugin": ["@react-native/gradle-plugin@0.86.2", "", {}, "sha512-2F6x14NcHMpVmfTTFKfMkpV5dZedZrLiv6PE+c3vgnesV2bjleUBydr4U+NI8VkI7OwW71L0A5qQ76I9LCrfoQ=="],
"@react-native/js-polyfills": ["@react-native/js-polyfills@0.86.0", "", {}, "sha512-zYy/Cjd1VTnZ2iCNaG9bDF9C3l2ntESiPRscjIlI5FKugu6aeTwsDSv1aI8Bc4Kp3vEdoVg+UQhLAhE4svREaQ=="],
"@react-native/js-polyfills": ["@react-native/js-polyfills@0.86.2", "", {}, "sha512-bIwNcGBaQ74shB5z1mRkxOpjikimuwsnOCEkZSzL67Z1FTyK1ObpENfyd2QvcvVW9Cjl+tHuw9ynpBnb2jPoJQ=="],
"@react-native/metro-babel-transformer": ["@react-native/metro-babel-transformer@0.86.0", "", { "dependencies": { "@babel/core": "^7.25.2", "@react-native/babel-preset": "0.86.0", "hermes-parser": "0.36.0", "nullthrows": "^1.1.1" } }, "sha512-SjKej3E5qIahqo/G+rSOrmJUQM44RyKtWtO+VfmKAAMoJWkBFomM22hTLKCIS5cdbIAJ9COAmU+KAi2wVSO0wQ=="],
"@react-native/metro-config": ["@react-native/metro-config@0.86.0", "", { "dependencies": { "@react-native/js-polyfills": "0.86.0", "@react-native/metro-babel-transformer": "0.86.0", "metro-config": "^0.84.3", "metro-runtime": "^0.84.3" } }, "sha512-7v+xbTeEci9ZcQ/Z1OqI4RXcqN69wSMDYL5BAMvOReZ7U04+aDQ0/SQhClYPn6x2/RxM4WzMKSAuNyLKqvYVtw=="],
"@react-native/normalize-colors": ["@react-native/normalize-colors@0.86.0", "", {}, "sha512-kG0wfCGghUKlfxkJyyHCDVutWVYWK7/DG58ojA/4v9EfulgF+osuSQmlbNb3rcKX58qutm7JcldSeVLgGFha9g=="],
"@react-native/normalize-colors": ["@react-native/normalize-colors@0.86.2", "", {}, "sha512-EzPFc9Y6lzYOWeso2almwXI7f8+qReHxWvT+algsOczb2UhWXIWXDoSvkdwoSfiwwmGt/ijJgKJoeHlzPkLwRg=="],
"@react-native/virtualized-lists": ["@react-native/virtualized-lists@0.86.0", "", { "dependencies": { "invariant": "^2.2.4", "nullthrows": "^1.1.1" }, "peerDependencies": { "@types/react": "^19.2.0", "react": "*", "react-native": "0.86.0" }, "optionalPeers": ["@types/react"] }, "sha512-4/ZLXdf/OSpPDVO0AsQ1SJdRIzt5t9BNQ46QwGgxvX7/cirYR5k8KXctNGGgW8lQo2gZChEfY2zFCZg9nM/jiw=="],
"@react-native/virtualized-lists": ["@react-native/virtualized-lists@0.86.2", "", { "dependencies": { "invariant": "^2.2.4", "nullthrows": "^1.1.1" }, "peerDependencies": { "@types/react": "^19.2.0", "react": "*", "react-native": "0.86.2" }, "optionalPeers": ["@types/react"] }, "sha512-uO0J72gh3EvE+1/GHRk18QRyBDTRHRB0AraAfojsRjbT7VMuJwKrZYaKGshavoaEud6aw00ZB9/8mTMIKjjcAw=="],
"@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.1.3", "", { "os": "android", "cpu": "arm64" }, "sha512-DT6Z3PhvioeHMvxo+xHc3KtqggrI7CCTXCmC2h/5zUlp5jVitv7XEy+9q5/7v8IolhlioawpMo8Kg0EEBy7J0g=="],
@@ -1373,7 +1374,7 @@
"@types/node": ["@types/node@26.0.1", "", { "dependencies": { "undici-types": "~8.3.0" } }, "sha512-fc3KiUoBt6kie0N9bIW3E47vZsuaMf0PM2AaUpLCLT0s/LvX1nxAim6Fc049cNxODPpGm6qRAuUOB86SkRuPQw=="],
"@types/react": ["@types/react@19.2.17", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw=="],
"@types/react": ["@types/react@19.2.18", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w=="],
"@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="],
@@ -1543,7 +1544,7 @@
"babel-plugin-transform-flow-enums": ["babel-plugin-transform-flow-enums@0.0.2", "", { "dependencies": { "@babel/plugin-syntax-flow": "^7.12.1" } }, "sha512-g4aaCrDDOsWjbm0PUUeVnkcVd6AKJsVc/MbnPhEotEpkeJQP6b8nzewohQi7+QS8UyPehOhGWn0nOwjvWpmMvQ=="],
"babel-preset-expo": ["babel-preset-expo@57.0.4", "", { "dependencies": { "@babel/generator": "^7.20.5", "@babel/helper-module-imports": "^7.25.9", "@babel/plugin-proposal-decorators": "^7.12.9", "@babel/plugin-proposal-export-default-from": "^7.24.7", "@babel/plugin-syntax-dynamic-import": "^7.8.3", "@babel/plugin-syntax-export-default-from": "^7.24.7", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", "@babel/plugin-syntax-optional-chaining": "^7.8.3", "@babel/plugin-transform-async-generator-functions": "^7.25.4", "@babel/plugin-transform-async-to-generator": "^7.24.7", "@babel/plugin-transform-block-scoping": "^7.25.0", "@babel/plugin-transform-class-properties": "^7.25.4", "@babel/plugin-transform-class-static-block": "^7.27.1", "@babel/plugin-transform-classes": "^7.25.4", "@babel/plugin-transform-destructuring": "^7.24.8", "@babel/plugin-transform-export-namespace-from": "^7.25.9", "@babel/plugin-transform-flow-strip-types": "^7.25.2", "@babel/plugin-transform-for-of": "^7.24.7", "@babel/plugin-transform-logical-assignment-operators": "^7.24.7", "@babel/plugin-transform-modules-commonjs": "^7.24.8", "@babel/plugin-transform-named-capturing-groups-regex": "^7.24.7", "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.7", "@babel/plugin-transform-object-rest-spread": "^7.24.7", "@babel/plugin-transform-optional-catch-binding": "^7.24.7", "@babel/plugin-transform-optional-chaining": "^7.24.8", "@babel/plugin-transform-parameters": "^7.24.7", "@babel/plugin-transform-private-methods": "^7.24.7", "@babel/plugin-transform-private-property-in-object": "^7.24.7", "@babel/plugin-transform-react-display-name": "^7.24.7", "@babel/plugin-transform-react-jsx": "^7.28.6", "@babel/plugin-transform-react-jsx-development": "^7.27.1", "@babel/plugin-transform-react-pure-annotations": "^7.27.1", "@babel/plugin-transform-runtime": "^7.24.7", "@babel/plugin-transform-typescript": "^7.25.2", "@babel/plugin-transform-unicode-regex": "^7.24.7", "@babel/preset-typescript": "^7.23.0", "@react-native/babel-plugin-codegen": "0.86.0", "babel-plugin-react-compiler": "^1.0.0", "babel-plugin-react-native-web": "~0.21.0", "babel-plugin-syntax-hermes-parser": "^0.36.0", "babel-plugin-transform-flow-enums": "^0.0.2", "debug": "^4.3.4" }, "peerDependencies": { "@babel/runtime": "^7.20.0", "expo": "*", "expo-widgets": "^57.0.6", "react-refresh": ">=0.14.0 <1.0.0" }, "optionalPeers": ["@babel/runtime", "expo", "expo-widgets"] }, "sha512-EkFcNoE23HVzQT6ZNXs/adN8+G7rqEAF6tQn6LpRPYa1YY7wmX5GxhJF0kaYMNtBndNrMTN4+0rYE17VG13KFg=="],
"babel-preset-expo": ["babel-preset-expo@57.0.5", "", { "dependencies": { "@babel/generator": "^7.20.5", "@babel/helper-module-imports": "^7.25.9", "@babel/plugin-proposal-decorators": "^7.12.9", "@babel/plugin-proposal-export-default-from": "^7.24.7", "@babel/plugin-syntax-dynamic-import": "^7.8.3", "@babel/plugin-syntax-export-default-from": "^7.24.7", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", "@babel/plugin-syntax-optional-chaining": "^7.8.3", "@babel/plugin-transform-async-generator-functions": "^7.25.4", "@babel/plugin-transform-async-to-generator": "^7.24.7", "@babel/plugin-transform-block-scoping": "^7.25.0", "@babel/plugin-transform-class-properties": "^7.25.4", "@babel/plugin-transform-class-static-block": "^7.27.1", "@babel/plugin-transform-classes": "^7.25.4", "@babel/plugin-transform-destructuring": "^7.24.8", "@babel/plugin-transform-export-namespace-from": "^7.25.9", "@babel/plugin-transform-flow-strip-types": "^7.25.2", "@babel/plugin-transform-for-of": "^7.24.7", "@babel/plugin-transform-logical-assignment-operators": "^7.24.7", "@babel/plugin-transform-modules-commonjs": "^7.24.8", "@babel/plugin-transform-named-capturing-groups-regex": "^7.24.7", "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.7", "@babel/plugin-transform-object-rest-spread": "^7.24.7", "@babel/plugin-transform-optional-catch-binding": "^7.24.7", "@babel/plugin-transform-optional-chaining": "^7.24.8", "@babel/plugin-transform-parameters": "^7.24.7", "@babel/plugin-transform-private-methods": "^7.24.7", "@babel/plugin-transform-private-property-in-object": "^7.24.7", "@babel/plugin-transform-react-display-name": "^7.24.7", "@babel/plugin-transform-react-jsx": "^7.28.6", "@babel/plugin-transform-react-jsx-development": "^7.27.1", "@babel/plugin-transform-react-pure-annotations": "^7.27.1", "@babel/plugin-transform-runtime": "^7.24.7", "@babel/plugin-transform-typescript": "^7.25.2", "@babel/plugin-transform-unicode-regex": "^7.24.7", "@babel/preset-typescript": "^7.23.0", "@react-native/babel-plugin-codegen": "0.86.2", "babel-plugin-react-compiler": "^1.0.0", "babel-plugin-react-native-web": "~0.21.0", "babel-plugin-syntax-hermes-parser": "^0.36.0", "babel-plugin-transform-flow-enums": "^0.0.2", "debug": "^4.3.4" }, "peerDependencies": { "@babel/runtime": "^7.20.0", "expo": "*", "expo-widgets": "^57.0.7", "react-refresh": ">=0.14.0 <1.0.0" }, "optionalPeers": ["@babel/runtime", "expo", "expo-widgets"] }, "sha512-sz9ZBTiUAlu5P9VORVNKoeAaY2qKFQRC6eQV5Y8aMkqcorvXKEv97UeCkFMLmMBEPKA+QeWImREKkX9/H21WQA=="],
"bail": ["bail@2.0.2", "", {}, "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw=="],
@@ -2109,12 +2110,14 @@
"expo-modules-jsi": ["expo-modules-jsi@57.0.3", "", { "peerDependencies": { "react-native": "*" } }, "sha512-B+iXJCC5OIXjFKkLLSY2DZ8BGS+hC3PBTrALpV43pHqhXqqZrRH3uTEylZgsoKQO8N8tjmjAua2ucTFHtr1o0w=="],
"expo-router": ["expo-router@57.0.8", "", { "dependencies": { "@expo/log-box": "^57.0.1", "@expo/metro-runtime": "^57.0.7", "@expo/schema-utils": "^57.0.2", "@expo/ui": "^57.0.7", "@radix-ui/react-slot": "^1.2.0", "@radix-ui/react-tabs": "^1.1.12", "@react-native-masked-view/masked-view": "^0.3.2", "@testing-library/jest-dom": "^6.9.1", "@testing-library/user-event": "^14.6.1", "client-only": "^0.0.1", "color": "^4.2.3", "debug": "^4.3.4", "escape-string-regexp": "^4.0.0", "expo-glass-effect": "^57.0.1", "expo-server": "^57.0.1", "expo-symbols": "^57.0.1", "fast-deep-equal": "^3.1.3", "invariant": "^2.2.4", "nanoid": "^3.3.8", "query-string": "^7.1.3", "react-fast-compare": "^3.2.2", "react-is": "^19.1.0", "react-native-drawer-layout": "^4.2.2", "react-native-screens": "^4.26.0", "server-only": "^0.0.1", "sf-symbols-typescript": "^2.1.0", "shallowequal": "^1.1.0", "standard-navigation": "^0.0.5", "vaul": "^1.1.2" }, "peerDependencies": { "@testing-library/react-native": ">= 13.2.0", "expo": "*", "expo-constants": "^57.0.7", "expo-linking": "^57.0.4", "react": "*", "react-dom": "*", "react-native": "*", "react-native-gesture-handler": "*", "react-native-reanimated": "*", "react-native-safe-area-context": ">= 5.4.0", "react-native-web": "*", "react-server-dom-webpack": "~19.0.4 || ~19.1.5 || ~19.2.4" }, "optionalPeers": ["@testing-library/react-native", "react-dom", "react-native-gesture-handler", "react-native-reanimated", "react-native-web", "react-server-dom-webpack"] }, "sha512-xAyTnZl597G9/r17GOuyTy6VlhjYCVmgzgmP00bhZ9b+VstPl3tTrOOhSFagVpeln47nKp7x7vgkANNheCv4eQ=="],
"expo-router": ["expo-router@57.0.9", "", { "dependencies": { "@expo/log-box": "^57.0.2", "@expo/metro-runtime": "^57.0.8", "@expo/schema-utils": "^57.0.2", "@expo/ui": "^57.0.8", "@radix-ui/react-slot": "^1.2.0", "@radix-ui/react-tabs": "^1.1.12", "@react-native-masked-view/masked-view": "^0.3.2", "@testing-library/jest-dom": "^6.9.1", "@testing-library/user-event": "^14.6.1", "client-only": "^0.0.1", "color": "^4.2.3", "debug": "^4.3.4", "escape-string-regexp": "^4.0.0", "expo-glass-effect": "^57.0.1", "expo-server": "^57.0.1", "expo-symbols": "^57.0.1", "fast-deep-equal": "^3.1.3", "invariant": "^2.2.4", "nanoid": "^3.3.8", "query-string": "^7.1.3", "react-fast-compare": "^3.2.2", "react-is": "^19.1.0", "react-native-drawer-layout": "^4.2.2", "react-native-screens": "^4.26.0", "server-only": "^0.0.1", "sf-symbols-typescript": "^2.1.0", "shallowequal": "^1.1.0", "standard-navigation": "^0.0.5", "vaul": "^1.1.2" }, "peerDependencies": { "@testing-library/react-native": ">= 13.2.0", "expo": "*", "expo-constants": "^57.0.8", "expo-linking": "^57.0.4", "react": "*", "react-dom": "*", "react-native": "*", "react-native-gesture-handler": "*", "react-native-reanimated": "*", "react-native-safe-area-context": ">= 5.4.0", "react-native-web": "*", "react-server-dom-webpack": "~19.0.4 || ~19.1.5 || ~19.2.4" }, "optionalPeers": ["@testing-library/react-native", "react-dom", "react-native-gesture-handler", "react-native-reanimated", "react-native-web", "react-server-dom-webpack"] }, "sha512-jPBwHmKmBfzCLUuKgFf+I/UhFkxvS3J2WF5MQKcuupC8XzQPBAqdzbxmcPIDY3VIVabHw7WnhwzsROWA74t1LA=="],
"expo-secure-store": ["expo-secure-store@57.0.1", "", { "peerDependencies": { "expo": "*" } }, "sha512-tLa1VmSadOq19mA/dwkl99RbHyjLE0T1qqBYMY3/OsguZTI+rlrDy/DDJjupqlVtmr95hD7o1pYqx5aL+B4YMA=="],
"expo-server": ["expo-server@57.0.1", "", {}, "sha512-sBfVDH6dmKVHZxqUxbfkzS00PZELMZt1IpnHKxcOTMZtR/t7CtRAFrbXcisG+EyzeqHSVDacZT+1tbYfZt5D8w=="],
"expo-sharing": ["expo-sharing@57.0.8", "", { "dependencies": { "@expo/config-plugins": "^57.0.6", "@expo/config-types": "^57.0.2", "@expo/plist": "^0.8.1" }, "peerDependencies": { "expo": "*", "react": "*", "react-native": "*" } }, "sha512-ZjiA09iJjzveachyBlbIijSH++fiacdvuenxb/gsdW21V3SUSxMQd+krPmafnhnMus5aBqpP0TXCBfBJN+0Bfg=="],
"expo-splash-screen": ["expo-splash-screen@57.0.4", "", { "dependencies": { "@expo/config-plugins": "~57.0.5", "@expo/image-utils": "^0.11.3", "xml2js": "0.6.0" }, "peerDependencies": { "expo": "*" } }, "sha512-AspaOA5VZUl8yXb/BQORrRdOs0f7F/IgmvKNdCFaUDuz4X+j/4bPkkyssARFKwC5A5IQ3g7QS/Ve1XxDiDZ38Q=="],
"expo-sqlite": ["expo-sqlite@57.0.1", "", { "dependencies": { "await-lock": "^2.2.2" }, "peerDependencies": { "expo": "*", "react": "*", "react-native": "*" } }, "sha512-I6KoUvfGIiROTKxr5D3H+jRIGA/iEvWEtqHK4XMukAA7tVTinz/YdS8zOz6/DdG6vgrNmvF7gyOcbhLinlfxzQ=="],
@@ -2323,7 +2326,7 @@
"hastscript": ["hastscript@9.0.1", "", { "dependencies": { "@types/hast": "^3.0.0", "comma-separated-tokens": "^2.0.0", "hast-util-parse-selector": "^4.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0" } }, "sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w=="],
"hermes-compiler": ["hermes-compiler@250829098.0.14", "", {}, "sha512-5meXwsZxgiqFaJjNzwjzI9IyUkuGGBisu+z9BvQWmGVpjH6nz11hgqkyxe4dl8UAdyIV4lTbz91+Dlnjz0VxqA=="],
"hermes-compiler": ["hermes-compiler@250829098.0.16", "", {}, "sha512-xsgzk+mUyvt9t1nUbF8USBlYxajTUtPJhVZ86q85s/SEoMKCF+52YZcudb0ENSnV3T3lV9mgB3s6R7+pH90zgw=="],
"hermes-estree": ["hermes-estree@0.36.0", "", {}, "sha512-A1+8zn5oss2CFP7pKsOaxorQG6FNIz1WU1VDqruLPPZl3LVgeE2C5xfFg8Ow6/Ow4mSslLLtYP1J3n38eKyW9w=="],
@@ -3185,7 +3188,7 @@
"react-is": ["react-is@19.2.7", "", {}, "sha512-kZFnouyVv7eP/Phmrlo9FK+zcAdriZJvzxXHF1Sl1P377WSGe2G/JxVolhTrB/jeV47lKImhNUsijjHAAbcl/A=="],
"react-native": ["react-native@0.86.0", "", { "dependencies": { "@react-native/assets-registry": "0.86.0", "@react-native/codegen": "0.86.0", "@react-native/community-cli-plugin": "0.86.0", "@react-native/gradle-plugin": "0.86.0", "@react-native/js-polyfills": "0.86.0", "@react-native/normalize-colors": "0.86.0", "@react-native/virtualized-lists": "0.86.0", "abort-controller": "^3.0.0", "anser": "^1.4.9", "ansi-regex": "^5.0.0", "babel-plugin-syntax-hermes-parser": "0.36.0", "base64-js": "^1.5.1", "commander": "^12.0.0", "flow-enums-runtime": "^0.0.6", "hermes-compiler": "250829098.0.14", "invariant": "^2.2.4", "memoize-one": "^5.0.0", "metro-runtime": "^0.84.3", "metro-source-map": "^0.84.3", "nullthrows": "^1.1.1", "pretty-format": "^29.7.0", "promise": "^8.3.0", "react-devtools-core": "^6.1.5", "react-refresh": "^0.14.0", "regenerator-runtime": "^0.13.2", "scheduler": "0.27.0", "semver": "^7.1.3", "stacktrace-parser": "^0.1.10", "tinyglobby": "^0.2.15", "whatwg-fetch": "^3.0.0", "ws": "^7.5.10", "yargs": "^17.6.2" }, "peerDependencies": { "@react-native/jest-preset": "0.86.0", "@types/react": "^19.1.1", "react": "^19.2.3" }, "optionalPeers": ["@react-native/jest-preset", "@types/react"], "bin": { "react-native": "cli.js" } }, "sha512-17ALh/dd6AO4pgOVmOO5Axll5PbErEo3XFyLokyzW6usyi+OShIEPwUW26wLPlhVifgSOIfECCH0WN+0IqtJ1w=="],
"react-native": ["react-native@0.86.2", "", { "dependencies": { "@react-native/assets-registry": "0.86.2", "@react-native/codegen": "0.86.2", "@react-native/community-cli-plugin": "0.86.2", "@react-native/gradle-plugin": "0.86.2", "@react-native/js-polyfills": "0.86.2", "@react-native/normalize-colors": "0.86.2", "@react-native/virtualized-lists": "0.86.2", "abort-controller": "^3.0.0", "anser": "^1.4.9", "ansi-regex": "^5.0.0", "babel-plugin-syntax-hermes-parser": "0.36.0", "base64-js": "^1.5.1", "commander": "^12.0.0", "flow-enums-runtime": "^0.0.6", "hermes-compiler": "250829098.0.16", "invariant": "^2.2.4", "memoize-one": "^5.0.0", "metro-runtime": "^0.84.3", "metro-source-map": "^0.84.3", "nullthrows": "^1.1.1", "pretty-format": "^29.7.0", "promise": "^8.3.0", "react-devtools-core": "^6.1.5", "react-refresh": "^0.14.0", "regenerator-runtime": "^0.13.2", "scheduler": "0.27.0", "semver": "^7.1.3", "stacktrace-parser": "^0.1.10", "tinyglobby": "^0.2.15", "whatwg-fetch": "^3.0.0", "ws": "^7.5.10", "yargs": "^17.6.2" }, "peerDependencies": { "@react-native/jest-preset": "0.86.2", "@types/react": "^19.1.1", "react": "^19.2.3" }, "optionalPeers": ["@react-native/jest-preset", "@types/react"], "bin": { "react-native": "cli.js" } }, "sha512-zbJXGZpwfZGA79Z9ob6Atvfx4nAQL8yJBa35s58E4Oo+khPykfQP2sTeumkKbjwajFYfVayg8pj7Il9nIfTk7A=="],
"react-native-drawer-layout": ["react-native-drawer-layout@4.2.7", "", { "dependencies": { "color": "^4.2.3", "use-latest-callback": "^0.2.4" }, "peerDependencies": { "react": ">= 18.2.0", "react-native": "*", "react-native-gesture-handler": ">= 2.0.0", "react-native-reanimated": ">= 2.0.0" } }, "sha512-hhD+E0QmUPkP2Sj1MsUdrvU7GeOiHChPAFPtKahroTwlBGnpgJsUVSL0GWOy5cG3oCZfnu4Pb+gIzOa4ItGNuA=="],
@@ -3863,7 +3866,7 @@
"xml2js": ["xml2js@0.6.0", "", { "dependencies": { "sax": ">=0.6.0", "xmlbuilder": "~11.0.0" } }, "sha512-eLTh0kA8uHceqesPqSE+VvO1CDDJWMwlQfB6LuN6T8w6MaDJ8Txm8P7s5cHD0miF0V+GGTZrDQfxPZQVsur33w=="],
"xmlbuilder": ["xmlbuilder@11.0.1", "", {}, "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA=="],
"xmlbuilder": ["xmlbuilder@15.1.1", "", {}, "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg=="],
"xxhash-wasm": ["xxhash-wasm@1.1.0", "", {}, "sha512-147y/6YNh+tlp6nd/2pWq38i9h6mz/EuQ6njIrmW8D1BS5nCqs0P6DG+m6zTGnNz5I+uhZ0SHxBs9BsPrwcKDA=="],
@@ -4017,7 +4020,7 @@
"@expo/package-manager/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
"@expo/plist/xmlbuilder": ["xmlbuilder@15.1.1", "", {}, "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg=="],
"@expo/prebuild-config/@react-native/normalize-colors": ["@react-native/normalize-colors@0.86.0", "", {}, "sha512-kG0wfCGghUKlfxkJyyHCDVutWVYWK7/DG58ojA/4v9EfulgF+osuSQmlbNb3rcKX58qutm7JcldSeVLgGFha9g=="],
"@expo/ws-tunnel/ws": ["ws@8.21.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g=="],
@@ -4053,14 +4056,20 @@
"@radix-ui/react-tabs/@radix-ui/react-roving-focus": ["@radix-ui/react-roving-focus@1.1.15", "", { "dependencies": { "@radix-ui/primitive": "1.1.5", "@radix-ui/react-collection": "1.1.12", "@radix-ui/react-compose-refs": "1.1.3", "@radix-ui/react-context": "1.2.0", "@radix-ui/react-direction": "1.1.2", "@radix-ui/react-id": "1.1.2", "@radix-ui/react-primitive": "2.1.7", "@radix-ui/react-use-callback-ref": "1.1.2", "@radix-ui/react-use-controllable-state": "1.2.3", "@radix-ui/react-use-is-hydrated": "0.1.1", "@radix-ui/react-use-layout-effect": "1.1.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-40svmmugfM3mUN7VUDGVE1tQGOhyi8enlGD0CNJEcMM36C1f71PKM21DFgNHUfem0XnA+d8H8oN3Z9ZpJjSslg=="],
"@react-native/babel-preset/@react-native/babel-plugin-codegen": ["@react-native/babel-plugin-codegen@0.86.0", "", { "dependencies": { "@babel/traverse": "^7.29.0", "@react-native/codegen": "0.86.0" } }, "sha512-qdsABWNW7uTll90l4Vh03gjeyu3WVDi2CyiiyvYGMRDcoYbjbQi6df3BMAm9lQI2yslZ1T14LlDDAsgTwNxplA=="],
"@react-native/babel-preset/react-refresh": ["react-refresh@0.14.2", "", {}, "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA=="],
"@react-native/codegen/yargs": ["yargs@17.7.3", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g=="],
"@react-native/community-cli-plugin/@react-native/dev-middleware": ["@react-native/dev-middleware@0.86.2", "", { "dependencies": { "@isaacs/ttlcache": "^1.4.1", "@react-native/debugger-frontend": "0.86.2", "@react-native/debugger-shell": "0.86.2", "chrome-launcher": "^0.15.2", "chromium-edge-launcher": "^0.3.0", "connect": "^3.6.5", "debug": "^4.4.0", "invariant": "^2.2.4", "nullthrows": "^1.1.1", "open": "^7.0.3", "serve-static": "^1.16.2", "ws": "^7.5.10" } }, "sha512-B7L0vKvg+IcEElT7Vpqh1xj5yJAqWUegjbP+bQRaorJMAYnv11GkliTnZV2AdTDfZQJWgOEx8i8LGkHkUg7bnA=="],
"@react-native/dev-middleware/chrome-launcher": ["chrome-launcher@0.15.2", "", { "dependencies": { "@types/node": "*", "escape-string-regexp": "^4.0.0", "is-wsl": "^2.2.0", "lighthouse-logger": "^1.0.0" }, "bin": { "print-chrome-path": "bin/print-chrome-path.js" } }, "sha512-zdLEwNo3aUVzIhKhTtXfxhdvZhUghrnmkvcAq2NoDd+LeOHKf03H5jwZ8T/STsAlzyALkBVK552iaG1fGf1xVQ=="],
"@react-native/dev-middleware/open": ["open@7.4.2", "", { "dependencies": { "is-docker": "^2.0.0", "is-wsl": "^2.1.1" } }, "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q=="],
"@react-native/metro-config/@react-native/js-polyfills": ["@react-native/js-polyfills@0.86.0", "", {}, "sha512-zYy/Cjd1VTnZ2iCNaG9bDF9C3l2ntESiPRscjIlI5FKugu6aeTwsDSv1aI8Bc4Kp3vEdoVg+UQhLAhE4svREaQ=="],
"@rollup/pluginutils/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="],
"@shikijs/primitive/@shikijs/types": ["@shikijs/types@4.3.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-oc8b9U2SYvofKZk8e/737nIX0qwf6eV2vHFATeObAu7r+mUVpLs8Re0BmVkIjAWAYgkmG/CzLNo7rzuBzRu/wQ=="],
@@ -4097,6 +4106,8 @@
"@tiptap/markdown/marked": ["marked@17.0.6", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-gB0gkNafnonOw0obSTEGZTT86IuhILt2Wfx0mWH/1Au83kybTayroZ/V6nS25mN7u8ASy+5fMhgB3XPNrOZdmA=="],
"@types/react-test-renderer/@types/react": ["@types/react@19.2.17", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw=="],
"addons-linter/css-tree": ["css-tree@3.2.1", "", { "dependencies": { "mdn-data": "2.27.1", "source-map-js": "^1.2.1" } }, "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA=="],
"addons-linter/upath": ["upath@3.0.7", "", {}, "sha512-VjBBquch25nUGMuVBpOb2Cj3gc8Kb7lJBqbsXR/0anZ/5uJsL14Kpth9JKfnBsckxCfgIp6hPvcvvmZ97R9X7g=="],
@@ -4219,10 +4230,14 @@
"expo-modules-autolinking/commander": ["commander@7.2.0", "", {}, "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw=="],
"expo-router/@expo/metro-runtime": ["@expo/metro-runtime@57.0.7", "", { "dependencies": { "@expo/log-box": "^57.0.1", "anser": "^1.4.9", "pretty-format": "^29.7.0", "stacktrace-parser": "^0.1.10", "whatwg-fetch": "^3.0.0" }, "peerDependencies": { "expo": "*", "react": "*", "react-dom": "*", "react-native": "*" }, "optionalPeers": ["react-dom"] }, "sha512-95UeoN/YsLellvskKsFGN9vKBwNc5k70ysO3skqfL3VusWlYIiYmPY+MpEWNz8A8W2CjLg9AKwZKAGrFj6znQg=="],
"expo-router/@expo/log-box": ["@expo/log-box@57.0.2", "", { "dependencies": { "@expo/dom-webview": "^57.0.1", "anser": "^1.4.9", "stacktrace-parser": "^0.1.10" }, "peerDependencies": { "expo": "*", "react": "*", "react-native": "*" } }, "sha512-ZsFyfIR7YCbQAdVLzuTUmMHofZC7ZS9ywYCJNPlLc78x59cI8GwXFEIVbRjjC0uJERpNtXx/tsNNnkhexXlzMw=="],
"expo-router/@expo/metro-runtime": ["@expo/metro-runtime@57.0.8", "", { "dependencies": { "@expo/log-box": "^57.0.2", "anser": "^1.4.9", "pretty-format": "^29.7.0", "stacktrace-parser": "^0.1.10", "whatwg-fetch": "^3.0.0" }, "peerDependencies": { "expo": "*", "react": "*", "react-dom": "*", "react-native": "*" }, "optionalPeers": ["react-dom"] }, "sha512-RrdehKXNtWpnm8nNs1QFhS0IauyMKR/nSQiry6dcUJs2T5EH8gYDMcMegUt9yI41K4nO3W8tr3O0xd/GZFObXQ=="],
"expo-router/react-native-screens": ["react-native-screens@4.26.2", "", { "dependencies": { "react-freeze": "^1.0.0", "warn-once": "^0.1.0" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-2XnWsZToKj76trGtEZzx5ELD/qOICFEprEeUntImmitQFVUkea27fiWdUSITArI356Y1qynpXZINW+Unbhky/A=="],
"expo-sharing/@expo/config-plugins": ["@expo/config-plugins@57.0.6", "", { "dependencies": { "@expo/config-types": "^57.0.2", "@expo/json-file": "~11.0.1", "@expo/plist": "^0.8.1", "@expo/require-utils": "^57.0.4", "@expo/sdk-runtime-versions": "^1.0.0", "chalk": "^4.1.2", "debug": "^4.3.5", "getenv": "^2.0.0", "glob": "^13.0.0", "semver": "^7.5.4", "slugify": "^1.6.6", "xcode": "^3.0.1", "xml2js": "0.6.0" } }, "sha512-7CmKrS5Rnu8aSZyNlxH2qzA7Ls1HEa4EQvEVOAkHDKPr1e4Cg/nz7I7dUl09QDTVjMvkKYDH4Th0DuAsgqASaw=="],
"expo-updates/arg": ["arg@4.1.3", "", {}, "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA=="],
"expo-updates/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
@@ -4343,8 +4358,6 @@
"pkijs/@noble/hashes": ["@noble/hashes@1.4.0", "", {}, "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg=="],
"plist/xmlbuilder": ["xmlbuilder@15.1.1", "", {}, "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg=="],
"postject/commander": ["commander@9.5.0", "", {}, "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ=="],
"pretty-format/ansi-styles": ["ansi-styles@5.2.0", "", {}, "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA=="],
@@ -4449,6 +4462,8 @@
"xcode/uuid": ["uuid@7.0.3", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg=="],
"xml2js/xmlbuilder": ["xmlbuilder@11.0.1", "", {}, "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA=="],
"yaml-language-server/request-light": ["request-light@0.5.8", "", {}, "sha512-3Zjgh+8b5fhRJBQZoy+zbVKpAQGLyka0MPgW3zruTF4dFFJ8Fqcfu9YsAvi/rvdcaTeWG3MkbZv4WKxAn/84Lg=="],
"yargs/yargs-parser": ["yargs-parser@22.0.0", "", {}, "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw=="],
@@ -4519,14 +4534,10 @@
"@electron/osx-sign/plist/@xmldom/xmldom": ["@xmldom/xmldom@0.9.10", "", {}, "sha512-A9gOqLdi6cV4ibazAjcQufGj0B1y/vDqYrcuP6d/6x8P27gRS8643Dj9o1dEKtB6O7fwxb2FgBmJS2mX7gpvdw=="],
"@electron/osx-sign/plist/xmlbuilder": ["xmlbuilder@15.1.1", "", {}, "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg=="],
"@electron/universal/minimatch/brace-expansion": ["brace-expansion@2.1.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA=="],
"@electron/universal/plist/@xmldom/xmldom": ["@xmldom/xmldom@0.9.10", "", {}, "sha512-A9gOqLdi6cV4ibazAjcQufGj0B1y/vDqYrcuP6d/6x8P27gRS8643Dj9o1dEKtB6O7fwxb2FgBmJS2mX7gpvdw=="],
"@electron/universal/plist/xmlbuilder": ["xmlbuilder@15.1.1", "", {}, "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg=="],
"@eslint/eslintrc/ajv/json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="],
"@eslint/eslintrc/espree/eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="],
@@ -4569,10 +4580,20 @@
"@radix-ui/react-tabs/@radix-ui/react-roving-focus/@radix-ui/react-collection": ["@radix-ui/react-collection@1.1.12", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.3", "@radix-ui/react-context": "1.2.0", "@radix-ui/react-primitive": "2.1.7", "@radix-ui/react-slot": "1.3.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-nb67INpE0IahJKN7EYPp9m9YGwYeKlnzxT3MwXVkgCskaSJia97kG4T0ywpjNUSSnoJk/uvk12V8vbrEHEj+/Q=="],
"@react-native/babel-preset/@react-native/babel-plugin-codegen/@react-native/codegen": ["@react-native/codegen@0.86.0", "", { "dependencies": { "@babel/core": "^7.25.2", "@babel/parser": "^7.29.0", "hermes-parser": "0.36.0", "invariant": "^2.2.4", "nullthrows": "^1.1.1", "tinyglobby": "^0.2.15", "yargs": "^17.6.2" } }, "sha512-uTs9DBo3+/lUqinsGZK0FKJRBVClrwMXoZToaDxE1Q2SL2e55vs2GwyZfIKzPl5uJnbu4PfFMIp0/mLXLWUMuA=="],
"@react-native/codegen/yargs/cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="],
"@react-native/codegen/yargs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
"@react-native/community-cli-plugin/@react-native/dev-middleware/@react-native/debugger-frontend": ["@react-native/debugger-frontend@0.86.2", "", {}, "sha512-KGS1aV5F6cIqpnoIUhLBXyVzy1oAj8jBFGau6vX4Vy0HXRJN7p+68RU7x6NuyraHvQcR14ccMGT5TkFuNjQ4gA=="],
"@react-native/community-cli-plugin/@react-native/dev-middleware/@react-native/debugger-shell": ["@react-native/debugger-shell@0.86.2", "", { "dependencies": { "cross-spawn": "^7.0.6", "debug": "^4.4.0", "fb-dotslash": "0.5.8" } }, "sha512-/TaVJ2+gGajZPJGrFaObUQmHmlaxAlfmOPZicl6pNKDUjzSgFMpcLkdTOExvb+USYTVdGX1XwxXyvjQdUO2bvg=="],
"@react-native/community-cli-plugin/@react-native/dev-middleware/chrome-launcher": ["chrome-launcher@0.15.2", "", { "dependencies": { "@types/node": "*", "escape-string-regexp": "^4.0.0", "is-wsl": "^2.2.0", "lighthouse-logger": "^1.0.0" }, "bin": { "print-chrome-path": "bin/print-chrome-path.js" } }, "sha512-zdLEwNo3aUVzIhKhTtXfxhdvZhUghrnmkvcAq2NoDd+LeOHKf03H5jwZ8T/STsAlzyALkBVK552iaG1fGf1xVQ=="],
"@react-native/community-cli-plugin/@react-native/dev-middleware/open": ["open@7.4.2", "", { "dependencies": { "is-docker": "^2.0.0", "is-wsl": "^2.1.1" } }, "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q=="],
"@react-native/dev-middleware/chrome-launcher/lighthouse-logger": ["lighthouse-logger@1.4.2", "", { "dependencies": { "debug": "^2.6.9", "marky": "^1.2.2" } }, "sha512-gPWxznF6TKmUHrOQjlVo2UbaL2EJ71mb2CCeRs/2qBpi4L/g4LUVc9+3lKQ6DTUZwJswfM7ainGrLO1+fOqa2g=="],
"@react-native/dev-middleware/open/is-docker": ["is-docker@2.2.1", "", { "bin": { "is-docker": "cli.js" } }, "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ=="],
@@ -4745,14 +4766,24 @@
"expo-modules-autolinking/chalk/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
"expo-sharing/@expo/config-plugins/@expo/require-utils": ["@expo/require-utils@57.0.4", "", { "dependencies": { "@babel/code-frame": "^7.20.0", "@babel/core": "^7.25.2", "@babel/plugin-transform-modules-commonjs": "^7.24.8" }, "peerDependencies": { "typescript": "^5.0.0 || ^5.0.0-0 || ^6.0.0 || ^7.0.0" }, "optionalPeers": ["typescript"] }, "sha512-e7xbg/9BTQcsZE/oErafZXtI7kh5IgfasLJ97J5sFSzX2cA74pDvdlhW1KHVSaDkQyQv6h1LSLhsY7dEeOk7hw=="],
"expo-sharing/@expo/config-plugins/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
"expo-sharing/@expo/config-plugins/glob": ["glob@13.0.6", "", { "dependencies": { "minimatch": "^10.2.2", "minipass": "^7.1.3", "path-scurry": "^2.0.2" } }, "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw=="],
"expo-updates/chalk/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
"expo-updates/glob/minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="],
"expo/babel-preset-expo/@react-native/babel-plugin-codegen": ["@react-native/babel-plugin-codegen@0.86.0", "", { "dependencies": { "@babel/traverse": "^7.29.0", "@react-native/codegen": "0.86.0" } }, "sha512-qdsABWNW7uTll90l4Vh03gjeyu3WVDi2CyiiyvYGMRDcoYbjbQi6df3BMAm9lQI2yslZ1T14LlDDAsgTwNxplA=="],
"filelist/minimatch/brace-expansion": ["brace-expansion@2.1.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA=="],
"finalhandler/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
"firefox-profile/xml2js/xmlbuilder": ["xmlbuilder@11.0.1", "", {}, "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA=="],
"hosted-git-info/lru-cache/yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="],
"jest-util/chalk/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
@@ -4849,8 +4880,6 @@
"simple-plist/plist/@xmldom/xmldom": ["@xmldom/xmldom@0.9.10", "", {}, "sha512-A9gOqLdi6cV4ibazAjcQufGj0B1y/vDqYrcuP6d/6x8P27gRS8643Dj9o1dEKtB6O7fwxb2FgBmJS2mX7gpvdw=="],
"simple-plist/plist/xmlbuilder": ["xmlbuilder@15.1.1", "", {}, "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg=="],
"sitemap/@types/node/undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="],
"svgo/css-tree/mdn-data": ["mdn-data@2.27.1", "", {}, "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ=="],
@@ -4891,12 +4920,18 @@
"@iconify/tools/svgo/css-tree/mdn-data": ["mdn-data@2.0.30", "", {}, "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA=="],
"@react-native/babel-preset/@react-native/babel-plugin-codegen/@react-native/codegen/yargs": ["yargs@17.7.3", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g=="],
"@react-native/codegen/yargs/cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
"@react-native/codegen/yargs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
"@react-native/codegen/yargs/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
"@react-native/community-cli-plugin/@react-native/dev-middleware/chrome-launcher/lighthouse-logger": ["lighthouse-logger@1.4.2", "", { "dependencies": { "debug": "^2.6.9", "marky": "^1.2.2" } }, "sha512-gPWxznF6TKmUHrOQjlVo2UbaL2EJ71mb2CCeRs/2qBpi4L/g4LUVc9+3lKQ6DTUZwJswfM7ainGrLO1+fOqa2g=="],
"@react-native/community-cli-plugin/@react-native/dev-middleware/open/is-docker": ["is-docker@2.2.1", "", { "bin": { "is-docker": "cli.js" } }, "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ=="],
"@react-native/dev-middleware/chrome-launcher/lighthouse-logger/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
"addons-linter/yargs/cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
@@ -4977,8 +5012,14 @@
"expo-linking/expo-constants/@expo/env/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
"expo-sharing/@expo/config-plugins/chalk/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
"expo-sharing/@expo/config-plugins/glob/minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="],
"expo-updates/glob/minimatch/brace-expansion": ["brace-expansion@5.0.6", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g=="],
"expo/babel-preset-expo/@react-native/babel-plugin-codegen/@react-native/codegen": ["@react-native/codegen@0.86.0", "", { "dependencies": { "@babel/core": "^7.25.2", "@babel/parser": "^7.29.0", "hermes-parser": "0.36.0", "invariant": "^2.2.4", "nullthrows": "^1.1.1", "tinyglobby": "^0.2.15", "yargs": "^17.6.2" } }, "sha512-uTs9DBo3+/lUqinsGZK0FKJRBVClrwMXoZToaDxE1Q2SL2e55vs2GwyZfIKzPl5uJnbu4PfFMIp0/mLXLWUMuA=="],
"log-symbols/chalk/ansi-styles/color-convert": ["color-convert@1.9.3", "", { "dependencies": { "color-name": "1.1.3" } }, "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg=="],
"log-symbols/chalk/supports-color/has-flag": ["has-flag@3.0.0", "", {}, "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw=="],
@@ -5015,16 +5056,46 @@
"@expo/metro-config/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="],
"@react-native/babel-preset/@react-native/babel-plugin-codegen/@react-native/codegen/yargs/cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="],
"@react-native/babel-preset/@react-native/babel-plugin-codegen/@react-native/codegen/yargs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
"@react-native/community-cli-plugin/@react-native/dev-middleware/chrome-launcher/lighthouse-logger/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
"@react-native/dev-middleware/chrome-launcher/lighthouse-logger/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
"expo-linking/expo-constants/@expo/env/chalk/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
"expo-sharing/@expo/config-plugins/glob/minimatch/brace-expansion": ["brace-expansion@5.0.6", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g=="],
"expo-updates/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="],
"expo/babel-preset-expo/@react-native/babel-plugin-codegen/@react-native/codegen/yargs": ["yargs@17.7.3", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g=="],
"log-symbols/chalk/ansi-styles/color-convert/color-name": ["color-name@1.1.3", "", {}, "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="],
"ora/chalk/ansi-styles/color-convert/color-name": ["color-name@1.1.3", "", {}, "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="],
"workbox-build/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="],
"@react-native/babel-preset/@react-native/babel-plugin-codegen/@react-native/codegen/yargs/cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
"@react-native/babel-preset/@react-native/babel-plugin-codegen/@react-native/codegen/yargs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
"@react-native/babel-preset/@react-native/babel-plugin-codegen/@react-native/codegen/yargs/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
"@react-native/community-cli-plugin/@react-native/dev-middleware/chrome-launcher/lighthouse-logger/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
"expo-sharing/@expo/config-plugins/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="],
"expo/babel-preset-expo/@react-native/babel-plugin-codegen/@react-native/codegen/yargs/cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="],
"expo/babel-preset-expo/@react-native/babel-plugin-codegen/@react-native/codegen/yargs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
"expo/babel-preset-expo/@react-native/babel-plugin-codegen/@react-native/codegen/yargs/cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
"expo/babel-preset-expo/@react-native/babel-plugin-codegen/@react-native/codegen/yargs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
"expo/babel-preset-expo/@react-native/babel-plugin-codegen/@react-native/codegen/yargs/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
}
}
+8
View File
@@ -29,4 +29,12 @@ describe("mobile app scope", () => {
expect(accountSecuritySource).not.toContain(removedCapability);
}
});
test("keeps version history reachable from an active note", () => {
expect(workspaceSource).toMatch(
/\{memo && !memo\.isDeleted \? \(\s*<Pressable\s+accessibilityLabel="版本历史"/
);
expect(workspaceSource).toContain('syncStatus === "conflict"');
expect(workspaceSource).toContain("onResolveSyncConflict");
});
});