Added Note Sharing Links
This commit is contained in:
+77
-11
@@ -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<{
|
||||
</div>
|
||||
}>
|
||||
{(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<{
|
||||
<SortableDesktopTab
|
||||
tab={tab}
|
||||
isSelected={props.selectedNoteId === tab.id}
|
||||
canEdit={canEdit}
|
||||
canEdit={canEdit()}
|
||||
/>
|
||||
)}
|
||||
</For>
|
||||
@@ -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<{
|
||||
>
|
||||
<Star size={14} class={cn(note().tags?.includes(getFavoriteTag()) && "fill-amber-500")} />
|
||||
</Button>
|
||||
<Show when={isOwner}>
|
||||
<Show when={isOwner()}>
|
||||
<Popover>
|
||||
<PopoverTrigger
|
||||
as={Button}
|
||||
@@ -518,6 +538,29 @@ export const NotepadView: Component<{
|
||||
<><Lock size={14} /> Private</>
|
||||
</Show>
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
class="w-full justify-start text-[0.625rem] font-bold uppercase tracking-widest gap-2 hover:bg-muted text-muted-foreground transition-all"
|
||||
onClick={async (e) => {
|
||||
e.stopPropagation();
|
||||
const url = `${window.location.origin}${window.location.pathname}#/embed/notes?noteId=${note().id}`;
|
||||
await navigator.clipboard.writeText(url);
|
||||
toast.success("Share link copied to clipboard");
|
||||
|
||||
if (note().isPrivate) {
|
||||
toast.info("Note is currently private. Make it public to allow others to see it?", {
|
||||
action: {
|
||||
label: "Make Public",
|
||||
onClick: () => handleUpdateNote(note().id, { isPrivate: false })
|
||||
}
|
||||
});
|
||||
}
|
||||
}}
|
||||
>
|
||||
<LinkIcon size={14} />
|
||||
Share Link
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
@@ -557,7 +600,7 @@ export const NotepadView: Component<{
|
||||
content={note().content}
|
||||
onUpdate={(html) => handleUpdateContent(note().id, html)}
|
||||
onEditorReady={setEditorInstance}
|
||||
editable={canEdit}
|
||||
editable={canEdit()}
|
||||
class={hasTopViewer() ? "[&>*:not(.file-viewer-wrapper:first-child,.file-attachment-wrapper:first-child)]:px-4 md:[&>*:not(.file-viewer-wrapper:first-child,.file-attachment-wrapper:first-child)]:px-6 [&>*:not(.file-viewer-wrapper:first-child,.file-attachment-wrapper:first-child)]:max-w-3xl [&>*:not(.file-viewer-wrapper:first-child,.file-attachment-wrapper:first-child)]:mx-auto [&>.file-viewer-wrapper:first-child]:h-[80vh] [&>.file-viewer-wrapper:first-child]:min-h-0 [&>.file-attachment-wrapper:first-child]:h-[80vh] [&>.file-attachment-wrapper:first-child]:min-h-0 pb-20" : ""}
|
||||
/>
|
||||
</div>
|
||||
@@ -699,7 +742,7 @@ export const NotepadView: Component<{
|
||||
<div class="px-4 py-4 border-t border-border/50 md:hidden flex items-center justify-between shrink-0 bg-background/95 backdrop-blur-sm z-40 w-full sticky bottom-0">
|
||||
<div class="flex items-center gap-2 w-16 shrink-0">
|
||||
{/* "..." popover menu containing Public/Private toggle and Delete */}
|
||||
<Show when={isOwner}>
|
||||
<Show when={isOwner()}>
|
||||
<Popover
|
||||
open={noteActionsOpen()}
|
||||
onOpenChange={(nextOpen) => {
|
||||
@@ -730,6 +773,29 @@ export const NotepadView: Component<{
|
||||
<><Lock size={14} /> Private</>
|
||||
</Show>
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
class="w-full justify-start text-[0.625rem] font-bold uppercase tracking-widest gap-2 hover:bg-muted text-muted-foreground transition-all"
|
||||
onClick={async (e) => {
|
||||
e.stopPropagation();
|
||||
const url = `${window.location.origin}${window.location.pathname}#/embed/notes?noteId=${note().id}`;
|
||||
await navigator.clipboard.writeText(url);
|
||||
toast.success("Share link copied to clipboard");
|
||||
|
||||
if (note().isPrivate) {
|
||||
toast.info("Note is currently private. Make it public?", {
|
||||
action: {
|
||||
label: "Make Public",
|
||||
onClick: () => handleUpdateNote(note().id, { isPrivate: false })
|
||||
}
|
||||
});
|
||||
}
|
||||
}}
|
||||
>
|
||||
<LinkIcon size={14} />
|
||||
Share Link
|
||||
</Button>
|
||||
<Show
|
||||
when={!confirmDeleteOpen() || isChildNote()}
|
||||
fallback={
|
||||
|
||||
Reference in New Issue
Block a user