a tool for shared writing and social publishing
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

added combobox and populated analytics page

celine f9bced1e 68ba2c37

+222 -186
-1
app/(home-pages)/p/[didOrHandle]/ProfileHeader.tsx
··· 42 42 @{props.profile.handle} 43 43 </div> 44 44 ); 45 - console.log(props.profile); 46 45 47 46 return ( 48 47 <div
-2
app/[leaflet_id]/publish/BskyPostEditorProsemirror.tsx
··· 410 410 range: { from: number; to: number }, 411 411 view: EditorView, 412 412 ) => { 413 - console.log("view", view); 414 413 if (!view) return; 415 414 const { from, to } = range; 416 415 const tr = view.state.tr; ··· 437 436 }); 438 437 tr.insert(from, atMentionNode); 439 438 } 440 - console.log("yo", mention); 441 439 442 440 // Add a space after the mention 443 441 tr.insertText(" ", from + 1);
-1
app/lish/[did]/[publication]/[rkey]/ThreadPage.tsx
··· 298 298 e.stopPropagation(); 299 299 300 300 props.toggleCollapsed(parentPostUri); 301 - console.log("reply clicked"); 302 301 }} 303 302 /> 304 303 </>
+165 -12
app/lish/[did]/[publication]/dashboard/PublicationAnalytics.tsx
··· 2 2 import { UpgradeContent } from "../UpgradeModal"; 3 3 import { Popover } from "components/Popover"; 4 4 import { DatePicker } from "components/DatePicker"; 5 - import { useState } from "react"; 5 + import { useMemo, useState } from "react"; 6 6 import { useLocalizedDate } from "src/hooks/useLocalizedDate"; 7 7 import type { DateRange } from "react-day-picker"; 8 8 import { usePublicationData } from "./PublicationSWRProvider"; 9 + import { 10 + Combobox, 11 + ComboboxResult, 12 + useComboboxState, 13 + } from "components/Combobox"; 14 + import { Input } from "components/Input"; 15 + 16 + type referrorType = { iconSrc: string; name: string; viewCount: string }; 17 + let refferors = [ 18 + { iconSrc: "", name: "Bluesky", viewCount: "12k" }, 19 + { iconSrc: "", name: "Reddit", viewCount: "1.2k" }, 20 + { iconSrc: "", name: "X", viewCount: "583" }, 21 + { iconSrc: "", name: "Google", viewCount: "12" }, 22 + ]; 9 23 10 24 export const PublicationAnalytics = () => { 11 25 let isPro = true; 12 26 13 27 let { data: publication } = usePublicationData(); 14 28 let [dateRange, setDateRange] = useState<DateRange>({ from: undefined }); 29 + let [selectedPost, setSelectedPost] = useState<string | undefined>(undefined); 30 + let [selectedReferror, setSelectedReferror] = useState< 31 + referrorType | undefined 32 + >(undefined); 15 33 16 34 if (!isPro) 17 35 return ( ··· 21 39 ); 22 40 23 41 return ( 24 - <div className="analytics"> 42 + <div className="analytics flex flex-col gap-6"> 43 + <div className="analyticsSubCount"> 44 + <div className="flex gap-2 justify-between items-center"> 45 + <h3>Subscribers</h3> 46 + <DateRangeSelector 47 + dateRange={dateRange} 48 + setDateRange={setDateRange} 49 + pubStartDate={publication?.publication?.indexed_at} 50 + /> 51 + </div> 52 + <div className="aspect-video w-full border border-border grow" /> 53 + </div> 25 54 <div className="analyticsViewCount"> 26 55 <div className="flex justify-between items-center gap-2 pb-2 w-full"> 27 56 <div className="flex gap-2 items-center"> 28 57 <h3>Traffic</h3> 29 - <ArrowRightTiny /> <PostSelector /> 58 + <ArrowRightTiny /> 59 + <PostSelector 60 + selectedPost={selectedPost} 61 + setSelectedPost={setSelectedPost} 62 + /> 63 + {selectedReferror && ( 64 + <> 65 + <ArrowRightTiny /> 66 + {selectedReferror.name} 67 + </> 68 + )} 30 69 </div> 31 70 <DateRangeSelector 32 71 dateRange={dateRange} ··· 34 73 pubStartDate={publication?.publication?.indexed_at} 35 74 /> 36 75 </div> 37 - <div className="aspect-video w-full border border-border" /> 76 + <div className="flex gap-2"> 77 + <div className="aspect-video w-full border border-border grow" /> 78 + <TopReferrors 79 + refferors={refferors} 80 + setSelectedReferror={setSelectedReferror} 81 + selectedReferror={selectedReferror} 82 + />{" "} 83 + </div> 38 84 </div> 39 - 40 - {/*<div>subscriber count over time</div> 41 - <div>Top Referrers</div>*/} 42 85 </div> 43 86 ); 44 87 }; 45 88 46 - const PostSelector = () => { 47 - return <div>Total</div>; 89 + const PostSelector = (props: { 90 + selectedPost: string | undefined; 91 + setSelectedPost: (s: string | undefined) => void; 92 + }) => { 93 + let { data } = usePublicationData(); 94 + let { documents } = data || {}; 95 + 96 + let [highlighted, setHighlighted] = useState<string | undefined>(undefined); 97 + let [searchValue, setSearchValue] = useState<string>(""); 98 + 99 + let open = useComboboxState((s) => s.open); 100 + let posts = documents?.map((doc) => doc.record.title); 101 + let filteredPosts = useMemo( 102 + () => 103 + posts && 104 + posts.filter((post) => 105 + post.toLowerCase().includes(searchValue.toLowerCase()), 106 + ), 107 + [searchValue], 108 + ); 109 + 110 + let filteredPostsWithClear = ["All Posts", ...(filteredPosts || [])]; 111 + 112 + return ( 113 + <Combobox 114 + trigger={ 115 + open ? ( 116 + <Input 117 + autoFocus 118 + placeholder="search posts…" 119 + className="input-with-border py-0! text-primary" 120 + value={searchValue} 121 + onChange={(e) => setSearchValue(e.target.value)} 122 + onClick={(e) => e.stopPropagation()} 123 + onPointerDown={(e) => e.stopPropagation()} 124 + /> 125 + ) : ( 126 + <button className="text-tertiary"> 127 + {props.selectedPost ?? "All Posts"} 128 + </button> 129 + ) 130 + } 131 + results={filteredPostsWithClear || []} 132 + highlighted={highlighted} 133 + setHighlighted={setHighlighted} 134 + onSelect={() => { 135 + props.setSelectedPost(highlighted); 136 + }} 137 + sideOffset={2} 138 + > 139 + {filteredPostsWithClear.map((post) => { 140 + if (post === "All Posts") 141 + return ( 142 + <> 143 + <ComboboxResult 144 + result={post} 145 + onSelect={() => { 146 + props.setSelectedPost(undefined); 147 + }} 148 + highlighted={highlighted} 149 + setHighlighted={setHighlighted} 150 + > 151 + All Posts 152 + </ComboboxResult> 153 + {filteredPosts && filteredPosts.length !== 0 && ( 154 + <hr className="mx-1 text-tertiary" /> 155 + )} 156 + </> 157 + ); 158 + return ( 159 + <ComboboxResult 160 + result={post} 161 + onSelect={() => { 162 + props.setSelectedPost(post); 163 + }} 164 + highlighted={highlighted} 165 + setHighlighted={setHighlighted} 166 + > 167 + {post} 168 + </ComboboxResult> 169 + ); 170 + })} 171 + </Combobox> 172 + ); 48 173 }; 49 174 50 175 const DateRangeSelector = (props: { ··· 56 181 "rounded-md px-1 text-sm border border-accent-contrast text-accent-contrast"; 57 182 58 183 let currentDate = new Date(); 59 - 60 - console.log("dateRange" + props.dateRange.from?.toISOString()); 61 - console.log("pubstart" + props.pubStartDate); 62 184 63 185 let startDate = useLocalizedDate( 64 186 props.dateRange.from?.toISOString() || ··· 133 255 </Popover> 134 256 ); 135 257 }; 258 + 259 + const TopReferrors = (props: { 260 + refferors: referrorType[]; 261 + setSelectedReferror: (ref: referrorType) => void; 262 + selectedReferror: referrorType | undefined; 263 + }) => { 264 + return ( 265 + <div className="topReferrors flex flex-col gap-0.5 w-full sm:w-xs"> 266 + {props.refferors.map((ref) => { 267 + let selected = ref === props.selectedReferror; 268 + return ( 269 + <> 270 + <button 271 + className={`w-full flex justify-between gap-4 px-1 items-center text-right rounded-md ${selected ? "text-accent-contrast bg-[var(--accent-light)]" : ""}`} 272 + onClick={() => { 273 + props.setSelectedReferror(ref); 274 + }} 275 + > 276 + <div className="flex gap-2 items-center grow"> 277 + <img src={ref.iconSrc} className="h-4 w-4" aria-hidden /> 278 + {ref.name} 279 + </div> 280 + {ref.viewCount} 281 + </button> 282 + <hr className="border-border-light last:hidden" /> 283 + </> 284 + ); 285 + })} 286 + </div> 287 + ); 288 + };
+3 -3
app/lish/[did]/[publication]/dashboard/PublicationSWRProvider.tsx
··· 12 12 13 13 // Derive all types from the RPC return type 14 14 export type PublicationData = GetPublicationDataReturnType["result"]; 15 - export type PublishedDocument = NonNullable<PublicationData>["documents"][number]; 15 + export type PublishedDocument = 16 + NonNullable<PublicationData>["documents"][number]; 16 17 export type PublicationDraft = NonNullable<PublicationData>["drafts"][number]; 17 18 18 19 const PublicationContext = createContext({ name: "", did: "" }); ··· 24 25 }) { 25 26 let key = `publication-data-${props.publication_did}-${props.publication_rkey}`; 26 27 useEffect(() => { 27 - console.log("UPDATING"); 28 28 mutate(key, props.publication_data); 29 29 }, [props.publication_data]); 30 30 return ( ··· 64 64 const { data } = usePublicationData(); 65 65 return useMemo( 66 66 () => normalizePublicationRecord(data?.publication?.record), 67 - [data?.publication?.record] 67 + [data?.publication?.record], 68 68 ); 69 69 } 70 70
-1
app/lish/[did]/[publication]/dashboard/PublicationSubscribers.tsx
··· 175 175 // ); 176 176 // props.setCheckedSubscribers(newCheckedSubscribers); 177 177 // } 178 - // console.log(props.checkedSubscribers); 179 178 // }} 180 179 // > 181 180 <>
-1
appview/index.ts
··· 374 374 embedRecord.embed?.media?.external?.uri?.includes(QUOTE_PARAM); 375 375 376 376 if (!hasQuoteParam) return; 377 - console.log("FOUND EMBED!!!"); 378 377 379 378 // Now validate the record since we know it contains our quote param 380 379 let record = AppBskyFeedPost.validateRecord(evt.record);
+54 -164
components/Blocks/BlockCommandBar.tsx
··· 1 - import { useEffect, useRef, useState } from "react"; 2 - import * as Popover from "@radix-ui/react-popover"; 1 + import { useState } from "react"; 3 2 import { blockCommands } from "./BlockCommands"; 4 3 import { useReplicache } from "src/replicache"; 5 4 import { useEntitySetContext } from "components/EntitySetProvider"; 6 - import { NestedCardThemeProvider } from "components/ThemeManager/ThemeProvider"; 7 - import { UndoManager } from "src/undoManager"; 8 5 import { useLeafletPublicationData } from "components/PageSWRDataProvider"; 9 6 import { setEditorState, useEditorStates } from "src/state/useEditorState"; 7 + import { Combobox, ComboboxResult } from "components/Combobox"; 10 8 11 9 type Props = { 12 10 parent: string; ··· 25 23 props: Props; 26 24 searchValue: string; 27 25 }) => { 28 - let ref = useRef<HTMLDivElement>(null); 29 - 30 26 let [highlighted, setHighlighted] = useState<string | undefined>(undefined); 31 27 32 28 let { rep, undoManager } = useReplicache(); ··· 51 47 const matchesName = command.name 52 48 .toLocaleLowerCase() 53 49 .includes(lowerSearchValue); 54 - const matchesAlternate = command.alternateNames?.some((altName) => 55 - altName.toLocaleLowerCase().includes(lowerSearchValue) 56 - ) ?? false; 50 + const matchesAlternate = 51 + command.alternateNames?.some((altName) => 52 + altName.toLocaleLowerCase().includes(lowerSearchValue), 53 + ) ?? false; 57 54 const matchesSearch = matchesName || matchesAlternate; 58 55 const isVisible = !pub || !command.hiddenInPublication; 59 56 return matchesSearch && isVisible; 60 57 }); 61 58 62 - useEffect(() => { 63 - if ( 64 - !highlighted || 65 - !commandResults.find((result) => result.name === highlighted) 66 - ) 67 - setHighlighted(commandResults[0]?.name); 68 - if (commandResults.length === 1) { 69 - setHighlighted(commandResults[0].name); 70 - } 71 - }, [commandResults, setHighlighted, highlighted]); 72 - 73 - useEffect(() => { 74 - let listener = async (e: KeyboardEvent) => { 75 - let reverseDir = ref.current?.dataset.side === "top"; 76 - let currentHighlightIndex = commandResults.findIndex( 77 - (command: { name: string }) => 78 - highlighted && command.name === highlighted, 79 - ); 80 - 81 - if (reverseDir ? e.key === "ArrowUp" : e.key === "ArrowDown") { 82 - setHighlighted( 83 - commandResults[ 84 - currentHighlightIndex === commandResults.length - 1 || 85 - currentHighlightIndex === undefined 86 - ? 0 87 - : currentHighlightIndex + 1 88 - ].name, 59 + return ( 60 + <Combobox 61 + triggerClassName="absolute left-0" 62 + results={commandResults.map((r) => r.name)} 63 + highlighted={highlighted} 64 + setHighlighted={setHighlighted} 65 + onSelect={async () => { 66 + let command = commandResults.find((c) => c.name === highlighted); 67 + if (!command || !rep) return; 68 + undoManager.startGroup(); 69 + await command.onSelect( 70 + rep, 71 + { ...props, entity_set: entity_set.set }, 72 + undoManager, 89 73 ); 90 - return; 91 - } 92 - if (reverseDir ? e.key === "ArrowDown" : e.key === "ArrowUp") { 93 - setHighlighted( 94 - commandResults[ 95 - currentHighlightIndex === 0 || 96 - currentHighlightIndex === undefined || 97 - currentHighlightIndex === -1 98 - ? commandResults.length - 1 99 - : currentHighlightIndex - 1 100 - ].name, 101 - ); 102 - return; 103 - } 104 - 105 - // on enter, select the highlighted item 106 - if (e.key === "Enter") { 107 - undoManager.startGroup(); 108 - e.preventDefault(); 109 - rep && 110 - (await commandResults[currentHighlightIndex]?.onSelect( 111 - rep, 112 - { 113 - ...props, 114 - entity_set: entity_set.set, 115 - }, 116 - undoManager, 117 - )); 118 74 undoManager.endGroup(); 119 - return; 120 - } 121 - }; 122 - 123 - window.addEventListener("keydown", listener); 124 - 125 - return () => window.removeEventListener("keydown", listener); 126 - }, [highlighted, setHighlighted, commandResults, rep, entity_set.set, props]); 127 - 128 - return ( 129 - <Popover.Root 130 - open 131 - onOpenChange={(open) => { 132 - if (!open) { 133 - clearCommandSearchText(); 134 - } 135 75 }} 76 + onOpenChange={() => clearCommandSearchText()} 136 77 > 137 - <Popover.Trigger className="absolute left-0"></Popover.Trigger> 138 - <Popover.Portal> 139 - <Popover.Content 140 - align="start" 141 - sideOffset={16} 142 - collisionPadding={16} 143 - ref={ref} 144 - onOpenAutoFocus={(e) => e.preventDefault()} 145 - className={` 146 - commandMenuContent group/cmd-menu 147 - z-20 w-[264px] 148 - flex data-[side=top]:items-end items-start 149 - `} 150 - > 151 - <NestedCardThemeProvider> 152 - <div className="commandMenuResults w-full max-h-(--radix-popover-content-available-height) overflow-auto flex flex-col group-data-[side=top]/cmd-menu:flex-col-reverse bg-bg-page py-1 gap-0.5 border border-border rounded-md shadow-md"> 153 - {commandResults.length === 0 ? ( 154 - <div className="w-full text-tertiary text-center italic py-2 px-2 "> 155 - No blocks found 156 - </div> 157 - ) : ( 158 - commandResults.map((result, index) => ( 159 - <div key={index} className="contents"> 160 - <CommandResult 161 - name={result.name} 162 - icon={result.icon} 163 - onSelect={() => { 164 - rep && 165 - result.onSelect( 166 - rep, 167 - { 168 - ...props, 169 - entity_set: entity_set.set, 170 - }, 171 - undoManager, 172 - ); 173 - }} 174 - highlighted={highlighted} 175 - setHighlighted={(highlighted) => 176 - setHighlighted(highlighted) 177 - } 178 - /> 179 - {commandResults[index + 1] && 180 - result.type !== commandResults[index + 1].type && ( 181 - <hr className="mx-2 my-0.5 border-border" /> 182 - )} 183 - </div> 184 - )) 78 + {commandResults.length === 0 ? ( 79 + <div className="w-full text-tertiary text-center italic py-2 px-2 "> 80 + No blocks found 81 + </div> 82 + ) : ( 83 + commandResults.map((result, index) => ( 84 + <div key={index} className="contents"> 85 + <ComboboxResult 86 + className="pl-1!" 87 + result={result.name} 88 + onSelect={() => { 89 + rep && 90 + result.onSelect( 91 + rep, 92 + { ...props, entity_set: entity_set.set }, 93 + undoManager, 94 + ); 95 + }} 96 + highlighted={highlighted} 97 + setHighlighted={setHighlighted} 98 + > 99 + <div className="text-tertiary w-8 shrink-0 flex justify-center"> 100 + {result.icon} 101 + </div> 102 + {result.name} 103 + </ComboboxResult> 104 + {commandResults[index + 1] && 105 + result.type !== commandResults[index + 1].type && ( 106 + <hr className="mx-2 my-0.5 border-border" /> 185 107 )} 186 - </div> 187 - </NestedCardThemeProvider> 188 - </Popover.Content> 189 - </Popover.Portal> 190 - </Popover.Root> 191 - ); 192 - }; 193 - 194 - const CommandResult = (props: { 195 - name: string; 196 - icon: React.ReactNode; 197 - onSelect: () => void; 198 - highlighted: string | undefined; 199 - setHighlighted: (state: string | undefined) => void; 200 - }) => { 201 - let isHighlighted = props.highlighted === props.name; 202 - 203 - return ( 204 - <button 205 - className={`commandResult menuItem text-secondary font-normal! py-0.5! mx-1 pl-0! ${isHighlighted && "bg-[var(--accent-light)]!"}`} 206 - onMouseOver={() => { 207 - props.setHighlighted(props.name); 208 - }} 209 - onMouseDown={(e) => { 210 - e.preventDefault(); 211 - props.onSelect(); 212 - }} 213 - > 214 - <div className="text-tertiary w-8 shrink-0 flex justify-center"> 215 - {props.icon} 216 - </div> 217 - {props.name} 218 - </button> 108 + </div> 109 + )) 110 + )} 111 + </Combobox> 219 112 ); 220 113 }; 221 - function usePublicationContext() { 222 - throw new Error("Function not implemented."); 223 - }
-1
components/Tags.tsx
··· 109 109 } 110 110 111 111 function selectTag(tag: string) { 112 - console.log("selected " + tag); 113 112 props.setSelectedTags([...props.selectedTags, tag]); 114 113 clearTagInput(); 115 114 }