diff --git a/apps/ios/EdgeEver.xcodeproj/project.pbxproj b/apps/ios/EdgeEver.xcodeproj/project.pbxproj index f70bd51a..af18460d 100644 --- a/apps/ios/EdgeEver.xcodeproj/project.pbxproj +++ b/apps/ios/EdgeEver.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + A1D1EC7F16E745A68CE9B6D1 /* AiDirectStream.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1D1EC7F16E745A68CE9B6D2 /* AiDirectStream.swift */; }; 039594C5BEE4844FBB131C7F /* APIClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6F510BF90B418266F363EBD /* APIClient.swift */; }; 0614453E8E9EC13B212B5A2F /* NotebookPickerSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3328D871910187ED827B6CE /* NotebookPickerSheet.swift */; }; 06D2661CAB0ADED27F322649 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = D87A8CED8498AE654EF8CD1F /* Localizable.xcstrings */; }; @@ -119,6 +120,7 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + A1D1EC7F16E745A68CE9B6D2 /* AiDirectStream.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AiDirectStream.swift; sourceTree = ""; }; 033565CE2E96EC2D23105188 /* ListActionsSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListActionsSheet.swift; sourceTree = ""; }; 0531FC6AA81DAF0A55B42362 /* AppDatabase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDatabase.swift; sourceTree = ""; }; 12236E3A2F318CDEB17B80CB /* SyncProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncProtocol.swift; sourceTree = ""; }; @@ -450,6 +452,7 @@ isa = PBXGroup; children = ( D6F510BF90B418266F363EBD /* APIClient.swift */, + A1D1EC7F16E745A68CE9B6D2 /* AiDirectStream.swift */, ); path = Network; sourceTree = ""; @@ -653,6 +656,7 @@ buildActionMask = 2147483647; files = ( 039594C5BEE4844FBB131C7F /* APIClient.swift in Sources */, + A1D1EC7F16E745A68CE9B6D1 /* AiDirectStream.swift in Sources */, 564B7A4D6CD3C8140286F881 /* AccountPasswordPanel.swift in Sources */, F6AACA31885AB69C1CCCCB46 /* AiAssistantSheet.swift in Sources */, 12F0F0D1E937AA88C33222FF /* ApiTokensView.swift in Sources */, diff --git a/apps/ios/EdgeEver/Data/Database/LocalMirrorRepository.swift b/apps/ios/EdgeEver/Data/Database/LocalMirrorRepository.swift index 4e851263..dfdf61ed 100644 --- a/apps/ios/EdgeEver/Data/Database/LocalMirrorRepository.swift +++ b/apps/ios/EdgeEver/Data/Database/LocalMirrorRepository.swift @@ -70,6 +70,12 @@ final class LocalMirrorRepository: @unchecked Sendable { } } + func saveNotebook(scope: String, notebook: Notebook) throws { + try dbQueue.write { db in + try upsertNotebook(db, scope: scope, notebook: notebook) + } + } + func listTags(scope: String) throws -> [TagSummary] { try dbQueue.read { db in let rows = try Row.fetchAll( diff --git a/apps/ios/EdgeEver/Data/Network/APIClient.swift b/apps/ios/EdgeEver/Data/Network/APIClient.swift index 28db42c4..86ea4674 100644 --- a/apps/ios/EdgeEver/Data/Network/APIClient.swift +++ b/apps/ios/EdgeEver/Data/Network/APIClient.swift @@ -99,6 +99,21 @@ actor APIClient { ) } + // MARK: - Notebooks + + func createNotebook(name: String, parentId: String?) async throws -> Notebook { + struct Body: Encodable { + var name: String + var parentId: String? + } + let response: NotebookResponse = try await request( + path: "/api/v1/notebooks", + method: "POST", + body: Body(name: name, parentId: parentId) + ) + return response.notebook + } + // MARK: - Memos func createMemo( diff --git a/apps/ios/EdgeEver/Features/Workspace/NotebookPickerSheet.swift b/apps/ios/EdgeEver/Features/Workspace/NotebookPickerSheet.swift index 9b2e35c3..904cb806 100644 --- a/apps/ios/EdgeEver/Features/Workspace/NotebookPickerSheet.swift +++ b/apps/ios/EdgeEver/Features/Workspace/NotebookPickerSheet.swift @@ -8,6 +8,11 @@ struct NotebookPickerSheet: View { @State private var query = "" @State private var collapsedIds: Set = [] + @State private var isCreating = false + @State private var isSaving = false + @State private var newName = "" + @State private var newParentId: String? + @State private var createError: String? private var parentIds: Set { NotebookHierarchy.parentIdsWithChildren(from: store.notebooks) @@ -55,6 +60,17 @@ struct NotebookPickerSheet: View { .foregroundStyle(AppTheme.secondary) } Spacer(minLength: 8) + Button { + isCreating.toggle() + createError = nil + } label: { + Image(systemName: "plus") + .font(.system(size: 17, weight: .semibold)) + .foregroundStyle(AppTheme.accent) + .frame(width: 36, height: 36) + } + .buttonStyle(.plain) + .accessibilityLabel(env.preferences.t("新建笔记本", en: "New notebook", ja: "新しいノートブック")) Button { dismiss() } label: { @@ -71,6 +87,51 @@ struct NotebookPickerSheet: View { Rectangle().fill(AppTheme.border).frame(height: 1) } + if isCreating { + VStack(alignment: .leading, spacing: 8) { + TextField(env.preferences.t("笔记本名称", en: "Notebook name", ja: "ノートブック名"), text: $newName) + .font(.system(size: 15)) + .foregroundStyle(AppTheme.title) + .textInputAutocapitalization(.sentences) + .padding(.horizontal, 12) + .frame(height: 42) + .overlay(RoundedRectangle(cornerRadius: 8).stroke(AppTheme.border)) + HStack(spacing: 8) { + parentChoice(env.preferences.t("顶层笔记本", en: "Top level", ja: "最上位"), id: nil) + if let selected = store.selectedNotebookId { + parentChoice(env.preferences.t("位于「\(activeName)」下", en: "Under \(activeName)", ja: "「\(activeName)」の下"), id: selected) + } + } + if let createError { + Text(createError) + .font(.system(size: 12)) + .foregroundStyle(.red) + .accessibilityAddTraits(.updatesFrequently) + } + Button { + Task { await createNotebook() } + } label: { + Group { + if isSaving { + ProgressView().tint(.white) + } else { + Text(env.preferences.t("创建笔记本", en: "Create notebook", ja: "ノートブックを作成")) + } + } + .font(.system(size: 14, weight: .bold)) + .foregroundStyle(.white) + .frame(maxWidth: .infinity) + .frame(height: 40) + .background(AppTheme.accent) + .clipShape(RoundedRectangle(cornerRadius: 8)) + } + .buttonStyle(.plain) + .disabled(isSaving || newName.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty) + } + .padding(.horizontal, 16) + .padding(.vertical, 12) + } + ScrollView { VStack(alignment: .leading, spacing: 0) { // Search @@ -194,9 +255,11 @@ struct NotebookPickerSheet: View { } } .background(AppTheme.card) - .presentationDetents([.medium, .large]) + .presentationDetents(isCreating ? [.large] : [.medium, .large]) .presentationDragIndicator(.hidden) .onAppear { + isCreating = store.startCreatingNotebook + store.startCreatingNotebook = false // Match Android: collapse branches not on the path to the active notebook. let parents = parentIds if let selected = store.selectedNotebookId { @@ -209,6 +272,48 @@ struct NotebookPickerSheet: View { } } + private func parentChoice(_ title: String, id: String?) -> some View { + Button { + newParentId = id + } label: { + Text(title) + .font(.system(size: 12, weight: .semibold)) + .foregroundStyle(AppTheme.title) + .lineLimit(1) + .frame(maxWidth: .infinity, minHeight: 36) + .overlay( + RoundedRectangle(cornerRadius: 7) + .stroke(newParentId == id ? AppTheme.accent : AppTheme.border) + ) + } + .buttonStyle(.plain) + .accessibilityAddTraits(newParentId == id ? .isSelected : []) + } + + private func createNotebook() async { + let name = newName.trimmingCharacters(in: .whitespacesAndNewlines) + guard !isSaving, !name.isEmpty, name.count <= 80 else { return } + isSaving = true + createError = nil + defer { isSaving = false } + do { + let notebook = try await env.session.client.createNotebook(name: name, parentId: newParentId) + if let scope = env.session.dataScope { + try? env.mirror.saveNotebook(scope: scope, notebook: notebook) + } + store.selectedTag = nil + store.selectedNotebookId = notebook.id + store.reload(env: env) + if !store.notebooks.contains(where: { $0.id == notebook.id }) { + store.notebooks.append(notebook) + } + dismiss() + Task { await env.runSyncCycle() } + } catch { + createError = error.localizedDescription + } + } + private func select(_ id: String?) { store.selectedTag = nil store.selectedNotebookId = id diff --git a/apps/ios/EdgeEver/Features/Workspace/NotesListView.swift b/apps/ios/EdgeEver/Features/Workspace/NotesListView.swift index 4c6b4105..0b06521c 100644 --- a/apps/ios/EdgeEver/Features/Workspace/NotesListView.swift +++ b/apps/ios/EdgeEver/Features/Workspace/NotesListView.swift @@ -7,6 +7,7 @@ struct NotesListView: View { @Binding var path: NavigationPath var onCreateNote: (() -> Void)? = nil var onCreateFromTemplate: (() -> Void)? = nil + var onCreateNotebook: (() -> Void)? = nil /// Whole-list settle + Pow jump once when data first becomes available this session. @State private var listEntranceSettled = false @@ -61,10 +62,12 @@ struct NotesListView: View { emptyCard( title: env.preferences.t("暂无笔记本", en: "No notebooks"), description: env.preferences.t( - "同步完成后,笔记本会出现在这里。可在桌面/Web 端创建笔记本。", - en: "Notebooks appear after sync. Create them on desktop/web." + "先创建一个笔记本,再开始记录。", + en: "Create a notebook to start taking notes.", + ja: "ノートブックを作成して記録を始めましょう。" ), - showCreate: false + showCreate: false, + showCreateNotebook: true ) .transition(Motion.softFade) } else if store.memos.isEmpty && !store.isLoadingList { @@ -440,7 +443,7 @@ struct NotesListView: View { } /// Android `memoListEmptyCard` + `emptyTitle` / `mutedText` + dual create actions. - private func emptyCard(title: String, description: String, showCreate: Bool) -> some View { + private func emptyCard(title: String, description: String, showCreate: Bool, showCreateNotebook: Bool = false) -> some View { VStack(spacing: 10) { Text(title) .font(.system(size: 16, weight: AppTheme.heavy)) @@ -449,6 +452,19 @@ struct NotesListView: View { .font(.system(size: 13)) .foregroundStyle(AppTheme.secondary) .multilineTextAlignment(.center) + if showCreateNotebook, let onCreateNotebook { + Button(action: onCreateNotebook) { + Label(env.preferences.t("新建笔记本", en: "New notebook", ja: "新しいノートブック"), systemImage: "plus") + .font(.system(size: 13, weight: .bold)) + .foregroundStyle(.white) + .padding(.horizontal, 14) + .frame(height: 38) + .background(AppTheme.accent) + .clipShape(RoundedRectangle(cornerRadius: 8)) + } + .buttonStyle(.plain) + .accessibilityIdentifier("emptyCreateNotebook") + } if showCreate, !store.notebooks.isEmpty { HStack(spacing: 8) { if let onCreateNote { diff --git a/apps/ios/EdgeEver/Features/Workspace/WorkspaceStore.swift b/apps/ios/EdgeEver/Features/Workspace/WorkspaceStore.swift index 3edd0f50..26a5663c 100644 --- a/apps/ios/EdgeEver/Features/Workspace/WorkspaceStore.swift +++ b/apps/ios/EdgeEver/Features/Workspace/WorkspaceStore.swift @@ -19,6 +19,7 @@ final class WorkspaceStore { var selectionMode = false var selectedMemoIds: Set = [] var showNotebookPicker = false + var startCreatingNotebook = false var showActions = false var showTagFilterPicker = false diff --git a/apps/ios/EdgeEver/Features/Workspace/WorkspaceView.swift b/apps/ios/EdgeEver/Features/Workspace/WorkspaceView.swift index f4de5772..34f78676 100644 --- a/apps/ios/EdgeEver/Features/Workspace/WorkspaceView.swift +++ b/apps/ios/EdgeEver/Features/Workspace/WorkspaceView.swift @@ -57,7 +57,11 @@ struct WorkspaceView: View { store: store, path: $path, onCreateNote: { openCreateNote() }, - onCreateFromTemplate: { openCreateFromTemplate() } + onCreateFromTemplate: { openCreateFromTemplate() }, + onCreateNotebook: { + store.startCreatingNotebook = true + store.showNotebookPicker = true + } ) .frame(maxWidth: .infinity, maxHeight: .infinity) } diff --git a/apps/mobile/src/lib/local-mirror.ts b/apps/mobile/src/lib/local-mirror.ts index bbd5a731..3a662a6a 100644 --- a/apps/mobile/src/lib/local-mirror.ts +++ b/apps/mobile/src/lib/local-mirror.ts @@ -84,6 +84,11 @@ export const listLocalNotebooks = async (scope: string): Promise<{ notebooks: No }; }; +export const upsertLocalNotebook = async (scope: string, notebook: Notebook) => { + const db = await getDatabase(); + await upsertNotebook(db, scope, notebook); +}; + export const listLocalTags = async (scope: string): Promise<{ tags: TagSummary[] }> => { const db = await getDatabase(); const rows = await db.getAllAsync( diff --git a/apps/mobile/src/lib/mobile-locale.tsx b/apps/mobile/src/lib/mobile-locale.tsx index 47f4ef76..1d207f8a 100644 --- a/apps/mobile/src/lib/mobile-locale.tsx +++ b/apps/mobile/src/lib/mobile-locale.tsx @@ -19,6 +19,17 @@ type MobileLocaleContextValue = { type TranslationPair = { source: string; target: string; pattern?: RegExp; placeholders?: string[] }; const mobileOnlyTranslations = new Map([ + ["新建笔记本", "New notebook"], + ["创建笔记本", "Create notebook"], + ["笔记本名称", "Notebook name"], + ["顶层笔记本", "Top level"], + ["位于「{{name}}」下", "Under {{name}}"], + ["暂无笔记本", "No notebooks"], + ["先创建一个笔记本,再开始记录。", "Create a notebook to start taking notes."], + ["创建笔记本失败,请重试", "Could not create the notebook. Try again."], + ["连接尚未就绪", "Connection is not ready"], + ["笔记本已创建", "Notebook created"], + ["本机缓存更新失败,正在重新同步。", "The local cache could not update. Syncing again now."], ["本级暂无笔记", "No notes directly in this notebook"], ["无法保存“父笔记本中显示子笔记本笔记”设置,请稍后重试", "Could not save the \"Show notes from sub-notebooks\" setting. Please try again."], ["子笔记本中还有 {{count}} 条笔记。可以打开子笔记本查看,或在设置中开启“父笔记本中显示子笔记本笔记”。", "Its sub-notebooks still contain {{count}} notes. Open a sub-notebook to see them, or turn on \"Show notes from sub-notebooks\" in Settings."], @@ -208,6 +219,17 @@ const mobileOnlyTranslations = new Map([ ]); const mobileOnlyJapanese = new Map([ + ["新建笔记本", "新しいノートブック"], + ["创建笔记本", "ノートブックを作成"], + ["笔记本名称", "ノートブック名"], + ["顶层笔记本", "最上位"], + ["位于「{{name}}」下", "「{{name}}」の下"], + ["暂无笔记本", "ノートブックがありません"], + ["先创建一个笔记本,再开始记录。", "ノートブックを作成して記録を始めましょう。"], + ["创建笔记本失败,请重试", "ノートブックを作成できませんでした。再試行してください。"], + ["连接尚未就绪", "接続の準備ができていません"], + ["笔记本已创建", "ノートブックを作成しました"], + ["本机缓存更新失败,正在重新同步。", "端末のキャッシュを更新できませんでした。再同期しています。"], ["本级暂无笔记", "このノートブック直下にノートはありません"], ["无法保存“父笔记本中显示子笔记本笔记”设置,请稍后重试", "「サブノートブックのノートを表示」の設定を保存できませんでした。しばらくしてから再試行してください。"], ["子笔记本中还有 {{count}} 条笔记。可以打开子笔记本查看,或在设置中开启“父笔记本中显示子笔记本笔记”。", "サブノートブックにはまだ {{count}} 件のノートがあります。サブノートブックを開くか、設定で「サブノートブックのノートを表示」をオンにしてください。"], diff --git a/apps/mobile/src/screens/WorkspaceNotesView.tsx b/apps/mobile/src/screens/WorkspaceNotesView.tsx index e97684d9..7c436887 100644 --- a/apps/mobile/src/screens/WorkspaceNotesView.tsx +++ b/apps/mobile/src/screens/WorkspaceNotesView.tsx @@ -197,16 +197,18 @@ export const NotesView = ({ 0 && !searchActive && !filterActive - ? [ - { label: "新建笔记", onPress: onCreate, variant: "primary" as const }, - ...(onCreateFromTemplate - ? [{ label: "从模板新建", onPress: onCreateFromTemplate, variant: "secondary" as const }] - : []), - ] + emptyActions={memoView === "notebook" && !searchActive && !filterActive + ? notebooks.length === 0 + ? [{ label: "新建笔记本", onPress: onOpenNotebookPicker, variant: "primary" as const }] + : [ + { label: "新建笔记", onPress: onCreate, variant: "primary" as const }, + ...(onCreateFromTemplate + ? [{ label: "从模板新建", onPress: onCreateFromTemplate, variant: "secondary" as const }] + : []), + ] : undefined} - emptyDescription={hiddenDescendantMemoCount > 0 ? `子笔记本中还有 ${hiddenDescendantMemoCount} 条笔记。可以打开子笔记本查看,或在设置中开启“父笔记本中显示子笔记本笔记”。` : searchActive ? "换个关键词再试" : filterActive ? "试试切换筛选条件,或调整搜索关键词。" : memoView === "trash" ? "删除的笔记会显示在这里。" : "先创建一条笔记,之后可以在这里快速预览、搜索和批量整理。"} - emptyTitle={hiddenDescendantMemoCount > 0 ? "本级暂无笔记" : searchActive ? "没有找到匹配笔记" : filterActive ? "没有符合筛选的笔记" : memoView === "trash" ? "回收站为空" : "暂无笔记"} + emptyDescription={hiddenDescendantMemoCount > 0 ? `子笔记本中还有 ${hiddenDescendantMemoCount} 条笔记。可以打开子笔记本查看,或在设置中开启“父笔记本中显示子笔记本笔记”。` : searchActive ? "换个关键词再试" : filterActive ? "试试切换筛选条件,或调整搜索关键词。" : memoView === "trash" ? "删除的笔记会显示在这里。" : notebooks.length === 0 ? "先创建一个笔记本,再开始记录。" : "先创建一条笔记,之后可以在这里快速预览、搜索和批量整理。"} + emptyTitle={hiddenDescendantMemoCount > 0 ? "本级暂无笔记" : searchActive ? "没有找到匹配笔记" : filterActive ? "没有符合筛选的笔记" : memoView === "trash" ? "回收站为空" : notebooks.length === 0 ? "暂无笔记本" : "暂无笔记"} error={error} initialSyncProgress={initialSyncProgress} isError={isError} diff --git a/apps/mobile/src/screens/WorkspacePickers.tsx b/apps/mobile/src/screens/WorkspacePickers.tsx index 91f1401e..1ed5a797 100644 --- a/apps/mobile/src/screens/WorkspacePickers.tsx +++ b/apps/mobile/src/screens/WorkspacePickers.tsx @@ -12,6 +12,7 @@ import { ChevronDown, ChevronRight, Folder, + Plus, Search, Tag, TagPlus, @@ -89,6 +90,7 @@ export const NotebookPickerModal = ({ includeAllNotes = true, notebooks, onClose, + onCreate, onSelect, visible, }: { @@ -96,12 +98,19 @@ export const NotebookPickerModal = ({ includeAllNotes?: boolean; notebooks: Notebook[]; onClose: () => void; + onCreate?: (name: string, parentId: string | null) => Promise; onSelect: (notebookId: string) => void; visible: boolean; }) => { const { translate } = useMobileLocale(); const safeAreaInsets = useSafeAreaInsets(); const [searchText, setSearchText] = useState(""); + const [creating, setCreating] = useState(false); + const [newNotebookName, setNewNotebookName] = useState(""); + const [newNotebookParentId, setNewNotebookParentId] = useState(null); + const [createError, setCreateError] = useState(""); + const [isSavingNotebook, setIsSavingNotebook] = useState(false); + const createInFlight = useRef(false); const [collapsedNotebookIds, setCollapsedNotebookIds] = useState>(() => new Set()); const selectedScroll = useAutoCenterSelectedScrollRow(visible, activeNotebookId); const notebookOptions = flattenNotebooks(notebooks); @@ -119,10 +128,33 @@ export const NotebookPickerModal = ({ useEffect(() => { if (visible) { setSearchText(""); + setCreating(false); + setNewNotebookName(""); + setNewNotebookParentId(null); + setCreateError(""); setCollapsedNotebookIds(new Set(Array.from(childNotebookIds).filter((notebookId) => !activeNotebookAncestorIds.has(notebookId)))); } }, [visible, activeNotebookId, notebooks]); + const submitNewNotebook = async () => { + const name = newNotebookName.trim(); + if (!onCreate || createInFlight.current || !name || name.length > 80) { + return; + } + createInFlight.current = true; + setIsSavingNotebook(true); + setCreateError(""); + try { + const notebookId = await onCreate(name, newNotebookParentId); + onSelect(notebookId); + } catch (error) { + setCreateError(error instanceof Error ? error.message : "创建笔记本失败,请重试"); + } finally { + createInFlight.current = false; + setIsSavingNotebook(false); + } + }; + const toggleNotebookCollapsed = (notebookId: string) => { setCollapsedNotebookIds((current) => { const next = new Set(current); @@ -156,6 +188,34 @@ export const NotebookPickerModal = ({ + {onCreate ? ( + + { setCreating((value) => !value); setCreateError(""); }} style={styles.notebookCreateToggle}> + + 新建笔记本 + + {creating ? ( + + + + setNewNotebookParentId(null)} style={[styles.notebookCreateParentChoice, newNotebookParentId === null && styles.notebookCreateParentChoiceActive]}> + 顶层笔记本 + + {activeNotebookId !== ALL_NOTES_ID ? ( + setNewNotebookParentId(activeNotebookId)} style={[styles.notebookCreateParentChoice, newNotebookParentId === activeNotebookId && styles.notebookCreateParentChoiceActive]}> + {translate(`位于「${activeNotebookName}」下`)} + + ) : null} + + {createError ? {createError} : null} + void submitNewNotebook()} style={styles.notebookCreateSubmit}> + {isSavingNotebook ? : 创建笔记本} + + + ) : null} + + ) : null} + setNotebookPickerOpen(false)} + onCreate={async (name, parentId) => { + if (!client) { + throw new Error("连接尚未就绪"); + } + const { notebook } = await client.createNotebook({ name, parentId }); + queryClient.setQueryData<{ notebooks: Notebook[] }>(["mobile", "notebooks"], (current) => ({ + notebooks: [...(current?.notebooks ?? []).filter((item) => item.id !== notebook.id), notebook], + })); + try { + await upsertLocalNotebook(dataScope, notebook); + void queryClient.invalidateQueries({ queryKey: ["mobile", "notebooks"] }); + } catch { + Alert.alert("笔记本已创建", "本机缓存更新失败,正在重新同步。", [{ text: "好的" }]); + void syncMobileLocalMirror(client, dataScope) + .then(() => queryClient.invalidateQueries({ queryKey: ["mobile", "notebooks"] })) + .catch(() => undefined); + } + return notebook.id; + }} onSelect={(notebookId) => { setSelectedTag(null); setActiveNotebookId(notebookId); diff --git a/apps/mobile/src/screens/workspace-styles.ts b/apps/mobile/src/screens/workspace-styles.ts index 6ec964b0..99b3748c 100644 --- a/apps/mobile/src/screens/workspace-styles.ts +++ b/apps/mobile/src/screens/workspace-styles.ts @@ -1541,6 +1541,73 @@ const baseWorkspaceStyles = StyleSheet.create({ justifyContent: "center", width: 32, }, + notebookCreateWrap: { + borderBottomColor: "#e2e8f0", + borderBottomWidth: 1, + paddingHorizontal: 16, + }, + notebookCreateToggle: { + alignItems: "center", + flexDirection: "row", + gap: 8, + minHeight: 44, + }, + notebookCreateToggleText: { + color: "#059669", + fontSize: 14, + fontWeight: "700", + }, + notebookCreateForm: { + gap: 8, + paddingBottom: 12, + }, + notebookCreateInput: { + borderColor: "#cbd5e1", + borderRadius: 8, + borderWidth: 1, + color: "#0f172a", + fontSize: 15, + minHeight: 42, + paddingHorizontal: 12, + }, + notebookCreateParentRow: { + flexDirection: "row", + gap: 8, + }, + notebookCreateParentChoice: { + borderColor: "#cbd5e1", + borderRadius: 7, + borderWidth: 1, + flex: 1, + justifyContent: "center", + minHeight: 36, + paddingHorizontal: 8, + }, + notebookCreateParentChoiceActive: { + borderColor: "#059669", + backgroundColor: "#ecfdf5", + }, + notebookCreateParentText: { + color: "#0f172a", + fontSize: 12, + fontWeight: "600", + }, + notebookCreateError: { + color: "#dc2626", + fontSize: 12, + }, + notebookCreateSubmit: { + alignItems: "center", + backgroundColor: "#059669", + borderRadius: 8, + justifyContent: "center", + minHeight: 40, + }, + notebookCreateSubmitText: { + color: "#ffffff", + fontSize: 14, + fontWeight: "700", + }, notebookPickerSectionHeader: { alignItems: "center", flexDirection: "row",