From 4414476816f8a6abccbeac08d2f5c0f1ee96648b Mon Sep 17 00:00:00 2001 From: Timothy Cardoza Date: Wed, 18 Mar 2026 18:22:30 -0500 Subject: [PATCH] Added Note Sharing Links --- src/App.tsx | 102 +++++++++++++++------------- src/components/EmbedAuthWrapper.tsx | 8 ++- src/store/index.ts | 24 +++++++ src/views/NotepadView.tsx | 88 +++++++++++++++++++++--- 4 files changed, 162 insertions(+), 60 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 3ec851c..97d9b0b 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -92,53 +92,63 @@ const App: Component = () => { return view; }; - return ( - { }} />}> - -
}> - - - - - - - - - setCurrentView("settings")} /> -
-
+ return ( + { }} />}> + +
}> + + + + + + + + + setCurrentView("settings")} /> +
+
+
+ } + > + {(() => { + const isPublicNote = () => { + const view = getEmbedView(); + const urlObj = new URL(location()); + const noteId = new URLSearchParams(urlObj.search).get('noteId') || + (urlObj.hash.includes('?') ? new URLSearchParams(urlObj.hash.substring(urlObj.hash.indexOf('?'))).get('noteId') : null); + return view === 'embed_notes' && !!noteId; + }; + + return ( + + +
}> + + {(() => { + const noteIdFromUrl = createMemo(() => { + const urlObj = new URL(location()); + let noteId = new URLSearchParams(urlObj.search).get('noteId'); + if (!noteId && urlObj.hash.includes('?')) { + const hashQueryString = urlObj.hash.substring(urlObj.hash.indexOf('?')); + noteId = new URLSearchParams(hashQueryString).get('noteId'); + } + return noteId; + }); + return { }} hideNavigation={!!noteIdFromUrl()} />; + })()} + + + + +
+
+
+ ); + })()}
- } - > - - -
}> - - {(() => { - const noteIdFromUrl = createMemo(() => { - const urlObj = new URL(location()); - let noteId = new URLSearchParams(urlObj.search).get('noteId'); - // Production likely uses hash routing, so check hash for query params too - if (!noteId && urlObj.hash.includes('?')) { - const hashQueryString = urlObj.hash.substring(urlObj.hash.indexOf('?')); - noteId = new URLSearchParams(hashQueryString).get('noteId'); - } - return noteId; - }); - console.log('[DEBUG App] Rendering embed/notes context. noteId trace:', noteIdFromUrl()); - return { }} hideNavigation={!!noteIdFromUrl()} />; - })()} - - - - -
-
-
- ); }; diff --git a/src/components/EmbedAuthWrapper.tsx b/src/components/EmbedAuthWrapper.tsx index cf21a75..6147a65 100644 --- a/src/components/EmbedAuthWrapper.tsx +++ b/src/components/EmbedAuthWrapper.tsx @@ -3,12 +3,14 @@ import { pb } from "@/lib/pocketbase"; interface EmbedAuthWrapperProps { children: JSX.Element; + skipAuth?: boolean; } export const EmbedAuthWrapper: Component = (props) => { - const [isHydrated, setIsHydrated] = createSignal(pb.authStore.isValid); + const [isHydrated, setIsHydrated] = createSignal(pb.authStore.isValid || !!props.skipAuth); const handleMessage = (event: MessageEvent) => { + if (props.skipAuth) return; // Don't process auth messages in skip mode const { data } = event; console.log('[DEBUG] EmbedAuthWrapper received message:', data?.type); if (data?.type === "TASGRID_AUTH" && data.token) { @@ -20,7 +22,7 @@ export const EmbedAuthWrapper: Component = (props) => { }; onMount(() => { - console.log('[DEBUG] EmbedAuthWrapper mounted. Initial isHydrated:', isHydrated()); + console.log('[DEBUG] EmbedAuthWrapper mounted. Initial isHydrated:', isHydrated(), 'skipAuth:', props.skipAuth); window.addEventListener("message", handleMessage); }); @@ -30,7 +32,7 @@ export const EmbedAuthWrapper: Component = (props) => { return (
diff --git a/src/store/index.ts b/src/store/index.ts index 732e377..af8af78 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -714,6 +714,30 @@ const mapRecordToNote = (r: any): Note => { }; }; +export const fetchNoteById = async (id: string): Promise => { + try { + const record = await pb.collection(NOTES_COLLECTION).getOne(id); + if (!record) return null; + return mapRecordToNote(record); + } catch (err) { + console.error("Failed to fetch note by ID:", err); + return null; + } +}; + +export const updateStoreWithNote = (note: Note) => { + setStore("notes", (currentNotes) => { + const index = currentNotes.findIndex(n => n.id === note.id); + if (index !== -1) { + const newNotes = [...currentNotes]; + newNotes[index] = note; + return newNotes; + } + return [...currentNotes, note]; + }); +}; + + // Helper to check if a task should be visible based on ownership, shares, or rules const shouldTaskBeVisible = (task: any, currentUserId: string): boolean => { // Own task diff --git a/src/views/NotepadView.tsx b/src/views/NotepadView.tsx index fbd42e5..9d621e3 100644 --- a/src/views/NotepadView.tsx +++ b/src/views/NotepadView.tsx @@ -1,5 +1,5 @@ import { type Component, createSignal, createMemo, createEffect, onCleanup, For, Show } from "solid-js"; -import { store, renameTagDefinition, updateNote, removeNote, restoreNote, setActiveNoteId, cleanupNoteAttachments, getFavoriteTag } from "@/store"; +import { store, renameTagDefinition, updateNote, removeNote, restoreNote, setActiveNoteId, cleanupNoteAttachments, getFavoriteTag, fetchNoteById, updateStoreWithNote } from "@/store"; import { pb } from "@/lib/pocketbase"; import { type Note } from "@/store"; import { Plus, X, Lock, Unlock, Trash2, ChevronDown, Link as LinkIcon, MoreHorizontal, Type, FileText, Search, Star, ChevronRight } from "lucide-solid"; @@ -28,7 +28,7 @@ export const NotepadView: Component<{ const [noteActionsOpen, setNoteActionsOpen] = createSignal(false); const [confirmDeleteOpen, setConfirmDeleteOpen] = createSignal(false); - const currentUserId = pb.authStore.model?.id; + const currentUserId = createMemo(() => pb.authStore.model?.id); // Sync activeNoteId for slash commands and run cleanup on unmount/change createEffect(() => { @@ -62,6 +62,25 @@ export const NotepadView: Component<{ }); }); + // Handle fetching note for unauthenticated users (public shares) + createEffect(async () => { + const id = props.selectedNoteId; + if (!id) return; + + // If note is already in store, we are good + const exists = store.notes.find(n => n.id === id); + if (exists) return; + + // If not authenticated or note just missing (e.g. direct link), try to fetch it + console.log("[NotepadView] Note not found in store, attempting public fetch for:", id); + const note = await fetchNoteById(id); + if (note) { + updateStoreWithNote(note); + } else if (!pb.authStore.isValid) { + toast.error("Note not found or you don't have permission to view it."); + } + }); + // Derived states const activeNote = createMemo(() => { const id = props.selectedNoteId?.trim(); @@ -206,7 +225,8 @@ export const NotepadView: Component<{ const handleCreateTab = async () => { const root = rootParentNote(); - if (!root || !currentUserId) return; + const userId = currentUserId(); + if (!root || !userId) return; try { const result = await pb.collection(NOTES_COLLECTION).create({ @@ -214,7 +234,7 @@ export const NotepadView: Component<{ content: "", tags: [`child-of-${root.id}`], isPrivate: root.isPrivate, // inherit privacy - user: currentUserId, + user: userId, }); props.setSelectedNoteId(result.id); } catch (e) { @@ -333,8 +353,8 @@ export const NotepadView: Component<{ }> {(note) => { - const isOwner = note().user === currentUserId; - const canEdit = isOwner || !note().isPrivate; + const isOwner = createMemo(() => note().user === currentUserId()); + const canEdit = createMemo(() => isOwner() || !note().isPrivate); const hasTopViewer = createMemo(() => { const c = note()?.content; @@ -383,7 +403,7 @@ export const NotepadView: Component<{ )} @@ -421,7 +441,7 @@ export const NotepadView: Component<{ )} value={root().title} placeholder="Note Title" - readOnly={!canEdit} + readOnly={!canEdit()} rows={1} onBlur={(e) => { const val = e.currentTarget.value.trim() || 'Untitled'; @@ -493,7 +513,7 @@ export const NotepadView: Component<{ > - + Private + +