tangled
alpha
login
or
join now
leaflet.pub
/
leaflet
a tool for shared writing and social publishing
284
fork
atom
overview
issues
27
pulls
pipelines
Added a page title to the home layout
cozylittle.house
1 week ago
2cb5eff0
12b52565
+139
-32
7 changed files
expand all
collapse all
unified
split
app
(home-pages)
home
HomeLayout.tsx
lish
[did]
[publication]
dashboard
PublicationDashboard.tsx
components
ActionBar
ActionButton.tsx
DesktopNavigation.tsx
NavigationButtons.tsx
Publications.tsx
PageLayouts
DashboardLayout.tsx
+64
app/(home-pages)/home/HomeLayout.tsx
···
18
DashboardLayout,
19
DashboardState,
20
useDashboardState,
0
21
} from "components/PageLayouts/DashboardLayout";
22
import { Actions } from "./Actions/Actions";
23
import { GetLeafletDataReturnType } from "app/api/rpc/[command]/get_leaflet_data";
···
28
HomeEmptyState,
29
PublicationBanner,
30
} from "./HomeEmpty/HomeEmpty";
0
0
0
0
0
0
31
32
export type Leaflet = {
33
added_at: string;
···
76
(leaflet) => leaflet.archived === true,
77
).length > 0;
78
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
79
return (
80
<DashboardLayout
81
id="home"
···
103
),
104
},
105
}}
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
106
/>
107
);
108
};
···
18
DashboardLayout,
19
DashboardState,
20
useDashboardState,
21
+
PageTitle,
22
} from "components/PageLayouts/DashboardLayout";
23
import { Actions } from "./Actions/Actions";
24
import { GetLeafletDataReturnType } from "app/api/rpc/[command]/get_leaflet_data";
···
29
HomeEmptyState,
30
PublicationBanner,
31
} from "./HomeEmpty/HomeEmpty";
32
+
import { Popover } from "components/Popover";
33
+
import { PubIcon, PublicationButtons } from "components/ActionBar/Publications";
34
+
import { normalizePublicationRecord } from "src/utils/normalizeRecords";
35
+
import { ButtonPrimary } from "components/Buttons";
36
+
import { LooseLeafSmall } from "components/Icons/LooseleafSmall";
37
+
import { HomeButton } from "components/ActionBar/NavigationButtons";
38
39
export type Leaflet = {
40
added_at: string;
···
83
(leaflet) => leaflet.archived === true,
84
).length > 0;
85
86
+
function getPubIcons() {
87
+
let hasLooseleafs = !!identity?.permission_token_on_homepage.find(
88
+
(f) =>
89
+
f.permission_tokens.leaflets_to_documents &&
90
+
f.permission_tokens.leaflets_to_documents[0]?.document,
91
+
);
92
+
93
+
if (identity && identity.publications.length >= 1) {
94
+
return (
95
+
<div className="flex gap-1">
96
+
{identity.publications.map((pub, index) => {
97
+
if (index <= 3)
98
+
return (
99
+
<PubIcon
100
+
key={pub.uri}
101
+
record={normalizePublicationRecord(pub.record)}
102
+
uri={pub.uri}
103
+
/>
104
+
);
105
+
})}
106
+
</div>
107
+
);
108
+
}
109
+
if (identity && hasLooseleafs) {
110
+
return (
111
+
<div className="bg-bg-leaflet rounded-full ">
112
+
<LooseLeafSmall className="scale-[75%]" />
113
+
</div>
114
+
);
115
+
} else
116
+
return (
117
+
<ButtonPrimary compact className="text-sm!">
118
+
Create a Publication!
119
+
</ButtonPrimary>
120
+
);
121
+
}
122
+
123
return (
124
<DashboardLayout
125
id="home"
···
147
),
148
},
149
}}
150
+
pageTitle={
151
+
<PageTitle
152
+
pageTitle={"Home"}
153
+
controls={
154
+
<Popover
155
+
trigger={<div>{getPubIcons()}</div>}
156
+
className="pt-1 px-2!"
157
+
>
158
+
<HomeButton current className="flex-row-reverse! justify-end!" />
159
+
<hr className="my-1 border-border-light" />
160
+
<PublicationButtons
161
+
currentPage={"home"}
162
+
currentPubUri={undefined}
163
+
className="justify-end!"
164
+
optionClassName=" flex-row-reverse!"
165
+
/>
166
+
</Popover>
167
+
}
168
+
/>
169
+
}
170
/>
171
);
172
};
+18
app/lish/[did]/[publication]/dashboard/PublicationDashboard.tsx
···
8
import { PublicationSubscribers } from "./PublicationSubscribers";
9
import {
10
DashboardLayout,
0
11
PublicationDashboardControls,
12
} from "components/PageLayouts/DashboardLayout";
13
import { useDebouncedEffect } from "src/hooks/useDebouncedEffect";
14
import { type NormalizedPublication } from "src/utils/normalizeRecords";
0
0
15
16
export default function PublicationDashboard({
17
publication,
···
76
actions={<Actions publication={publication.uri} />}
77
currentPage="pub"
78
publication={publication.uri}
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
79
/>
80
);
81
}
···
8
import { PublicationSubscribers } from "./PublicationSubscribers";
9
import {
10
DashboardLayout,
11
+
PageTitle,
12
PublicationDashboardControls,
13
} from "components/PageLayouts/DashboardLayout";
14
import { useDebouncedEffect } from "src/hooks/useDebouncedEffect";
15
import { type NormalizedPublication } from "src/utils/normalizeRecords";
16
+
import { PublicationButtons } from "components/ActionBar/Publications";
17
+
import { Popover } from "components/Popover";
18
19
export default function PublicationDashboard({
20
publication,
···
79
actions={<Actions publication={publication.uri} />}
80
currentPage="pub"
81
publication={publication.uri}
82
+
pageTitle={
83
+
<PageTitle
84
+
pageTitle={record.name}
85
+
controls={
86
+
<Popover trigger={<div>pubs</div>} className="pt-1 px-2!">
87
+
<PublicationButtons
88
+
currentPage={"pub"}
89
+
currentPubUri={publication.uri}
90
+
className="justify-end!"
91
+
optionClassName=" flex-row-reverse!"
92
+
/>
93
+
</Popover>
94
+
}
95
+
/>
96
+
}
97
/>
98
);
99
}
+1
-1
components/ActionBar/ActionButton.tsx
···
71
>
72
<div className="shrink-0 flex flex-row gap-0.5">{icon}</div>
73
<div
74
-
className={`flex flex-col pr-1 ${subtext && "leading-snug"} max-w-full min-w-0 ${sidebar.open ? "block" : showLabelOnMobile ? "sm:hidden block" : "hidden"}`}
75
>
76
<div className="truncate text-left">{label}</div>
77
{subtext && (
···
71
>
72
<div className="shrink-0 flex flex-row gap-0.5">{icon}</div>
73
<div
74
+
className={`flex flex-col ${subtext && "leading-snug"} max-w-full min-w-0 ${sidebar.open ? "block" : showLabelOnMobile ? "sm:hidden block" : "hidden"}`}
75
>
76
<div className="truncate text-left">{label}</div>
77
{subtext && (
+8
-4
components/ActionBar/DesktopNavigation.tsx
···
8
} from "./NavigationButtons";
9
import { PublicationButtons } from "./Publications";
10
import { Sidebar } from "./Sidebar";
0
11
12
export const DesktopNavigation = (props: {
13
currentPage: navPages;
···
24
props.currentPage === "pub";
25
return (
26
<div className="flex flex-col gap-3">
27
-
{identity?.atp_did && (
28
-
<Sidebar alwaysOpen>
29
<NotificationButton current={props.currentPage === "notifications"} />
30
-
</Sidebar>
31
-
)}
0
0
0
32
<Sidebar alwaysOpen>
33
<ReaderButton
34
current={props.currentPage === "reader"}
···
8
} from "./NavigationButtons";
9
import { PublicationButtons } from "./Publications";
10
import { Sidebar } from "./Sidebar";
11
+
import { LoginActionButton, LoginButton } from "components/LoginButton";
12
13
export const DesktopNavigation = (props: {
14
currentPage: navPages;
···
25
props.currentPage === "pub";
26
return (
27
<div className="flex flex-col gap-3">
28
+
<Sidebar alwaysOpen>
29
+
{identity?.atp_did ? (
30
<NotificationButton current={props.currentPage === "notifications"} />
31
+
) : (
32
+
<LoginActionButton />
33
+
)}
34
+
</Sidebar>
35
+
36
<Sidebar alwaysOpen>
37
<ReaderButton
38
current={props.currentPage === "reader"}
+11
-19
components/ActionBar/NavigationButtons.tsx
···
23
| "notifications"
24
| "looseleafs"
25
| "tag"
26
-
| "profile";
0
27
28
-
export const HomeButton = (props: { current?: boolean }) => {
0
0
0
29
return (
30
<SpeedyLink href={"/home"} className="hover:!no-underline">
31
<ActionButton
32
nav
33
icon={<HomeSmall />}
34
-
label="Writer Home"
35
-
className={props.current ? "bg-bg-page! border-border-light!" : ""}
36
/>
37
</SpeedyLink>
38
);
···
82
nav
83
labelOnMobile={true}
84
icon={<WriterSmall />}
85
-
label=<div className="flex flex-row gap-1">
86
-
Writer
87
-
{current && (
88
-
<>
89
-
<Divider /> {currentIcon}
90
-
</>
91
-
)}
92
-
</div>
93
className={current ? "bg-bg-page! border-border-light!" : ""}
94
/>
95
) : (
···
99
icon={
100
<>
101
<WriterSmall />
102
-
{current && (
103
-
<>
104
-
<Divider /> {currentIcon}
105
-
</>
106
-
)}
107
</>
108
}
109
label=<div className="flex flex-row gap-1">Writer</div>
···
116
<ActionButton
117
nav
118
icon={<HomeSmall />}
119
-
label="Writer Home"
120
className={
121
props.currentPage === "home"
122
? "bg-bg-page! border-border-light!"
···
145
nav
146
labelOnMobile={props.compactOnMobile}
147
icon={<ReaderUnreadSmall />}
148
-
label="Reader"
149
className={props.current ? "bg-bg-page! border-border-light!" : ""}
150
/>
151
</SpeedyLink>
···
23
| "notifications"
24
| "looseleafs"
25
| "tag"
26
+
| "profile"
27
+
| "discover";
28
29
+
export const HomeButton = (props: {
30
+
current?: boolean;
31
+
className?: string;
32
+
}) => {
33
return (
34
<SpeedyLink href={"/home"} className="hover:!no-underline">
35
<ActionButton
36
nav
37
icon={<HomeSmall />}
38
+
label="Write"
39
+
className={`${props.current ? "bg-bg-page! border-border-light!" : ""} ${props.className}`}
40
/>
41
</SpeedyLink>
42
);
···
86
nav
87
labelOnMobile={true}
88
icon={<WriterSmall />}
89
+
label=<div className="flex flex-row gap-1">Write</div>
0
0
0
0
0
0
0
90
className={current ? "bg-bg-page! border-border-light!" : ""}
91
/>
92
) : (
···
96
icon={
97
<>
98
<WriterSmall />
0
0
0
0
0
99
</>
100
}
101
label=<div className="flex flex-row gap-1">Writer</div>
···
108
<ActionButton
109
nav
110
icon={<HomeSmall />}
111
+
label="Write"
112
className={
113
props.currentPage === "home"
114
? "bg-bg-page! border-border-light!"
···
137
nav
138
labelOnMobile={props.compactOnMobile}
139
icon={<ReaderUnreadSmall />}
140
+
label="Read"
141
className={props.current ? "bg-bg-page! border-border-light!" : ""}
142
/>
143
</SpeedyLink>
+16
-7
components/ActionBar/Publications.tsx
···
20
import { useState } from "react";
21
import { LooseLeafSmall } from "components/Icons/LooseleafSmall";
22
import type { navPages } from "./NavigationButtons";
0
23
24
export const PublicationButtons = (props: {
25
currentPage: navPages;
26
currentPubUri: string | undefined;
0
0
27
}) => {
28
let { identity } = useIdentityData();
29
let hasLooseleafs = !!identity?.permission_token_on_homepage.find(
···
38
return <PubListEmpty />;
39
40
return (
41
-
<div className="pubListWrapper w-full flex flex-col sm:bg-transparent sm:border-0">
0
0
42
{hasLooseleafs && (
43
<>
44
<SpeedyLink
45
href={`/looseleafs`}
46
-
className="flex gap-2 items-start text-secondary font-bold hover:no-underline! hover:text-accent-contrast w-full"
47
>
48
{/*TODO How should i get if this is the current page or not?
49
theres not "pub" to check the uri for. Do i need to add it as an option to NavPages? thats kinda annoying*/}
···
51
label="Looseleafs"
52
icon={<LooseLeafSmall />}
53
nav
54
-
className={
55
props.currentPage === "looseleafs"
56
? "bg-bg-page! border-border!"
57
: ""
58
}
0
59
/>
60
</SpeedyLink>
61
</>
···
68
key={d.uri}
69
record={d.record}
70
current={d.uri === props.currentPubUri}
0
71
/>
72
);
73
})}
74
<Link
75
href={"/lish/createPub"}
76
-
className="pubListCreateNew text-accent-contrast text-sm place-self-end hover:text-accent-contrast"
77
>
78
-
New
0
79
</Link>
80
</div>
81
);
···
86
name: string;
87
record: Json;
88
current?: boolean;
0
89
}) => {
90
let record = normalizePublicationRecord(props.record);
91
if (!record) return;
···
93
return (
94
<SpeedyLink
95
href={`${getBasePublicationURL(props)}/dashboard`}
96
-
className="flex gap-2 items-start text-secondary font-bold hover:no-underline! hover:text-accent-contrast w-full"
97
>
98
<ActionButton
99
label={record.name}
100
icon={<PubIcon record={record} uri={props.uri} />}
101
nav
102
-
className={props.current ? "bg-bg-page! border-border!" : ""}
103
/>
104
</SpeedyLink>
105
);
···
20
import { useState } from "react";
21
import { LooseLeafSmall } from "components/Icons/LooseleafSmall";
22
import type { navPages } from "./NavigationButtons";
23
+
import { AddTiny } from "components/Icons/AddTiny";
24
25
export const PublicationButtons = (props: {
26
currentPage: navPages;
27
currentPubUri: string | undefined;
28
+
className?: string;
29
+
optionClassName?: string;
30
}) => {
31
let { identity } = useIdentityData();
32
let hasLooseleafs = !!identity?.permission_token_on_homepage.find(
···
41
return <PubListEmpty />;
42
43
return (
44
+
<div
45
+
className={`pubListWrapper w-full flex flex-col sm:bg-transparent sm:border-0 ${props.className}`}
46
+
>
47
{hasLooseleafs && (
48
<>
49
<SpeedyLink
50
href={`/looseleafs`}
51
+
className={`flex gap-2 items-start text-secondary font-bold hover:no-underline! hover:text-accent-contrast w-full `}
52
>
53
{/*TODO How should i get if this is the current page or not?
54
theres not "pub" to check the uri for. Do i need to add it as an option to NavPages? thats kinda annoying*/}
···
56
label="Looseleafs"
57
icon={<LooseLeafSmall />}
58
nav
59
+
className={`${
60
props.currentPage === "looseleafs"
61
? "bg-bg-page! border-border!"
62
: ""
63
}
64
+
${props.optionClassName}`}
65
/>
66
</SpeedyLink>
67
</>
···
74
key={d.uri}
75
record={d.record}
76
current={d.uri === props.currentPubUri}
77
+
className={props.optionClassName}
78
/>
79
);
80
})}
81
<Link
82
href={"/lish/createPub"}
83
+
className={`pubListCreateNew group/new-pub text-tertiary hover:text-accent-contrast flex gap-2 items-center p-1 no-underline! ${props.optionClassName}`}
84
>
85
+
<div className="group-hover/new-pub:border-accent-contrast w-6 h-6 border-border-light border-2 border-dashed rounded-full" />
86
+
New Publication
87
</Link>
88
</div>
89
);
···
94
name: string;
95
record: Json;
96
current?: boolean;
97
+
className?: string;
98
}) => {
99
let record = normalizePublicationRecord(props.record);
100
if (!record) return;
···
102
return (
103
<SpeedyLink
104
href={`${getBasePublicationURL(props)}/dashboard`}
105
+
className={`flex gap-2 items-start text-secondary font-bold hover:no-underline! hover:text-accent-contrast w-full `}
106
>
107
<ActionButton
108
label={record.name}
109
icon={<PubIcon record={record} uri={props.uri} />}
110
nav
111
+
className={`${props.current ? "bg-bg-page! border-border!" : ""} ${props.className}`}
112
/>
113
</SpeedyLink>
114
);
+21
-1
components/PageLayouts/DashboardLayout.tsx
···
27
import { ExternalLinkTiny } from "components/Icons/ExternalLinkTiny";
28
import { usePreserveScroll } from "src/hooks/usePreserveScroll";
29
import { Tab } from "components/Tab";
0
30
31
export type DashboardState = {
32
display?: "grid" | "list";
···
141
publication?: string;
142
profileDid?: string;
143
actions: React.ReactNode;
0
144
}) {
145
const searchParams = useSearchParams();
146
const tabParam = searchParams.get("tab");
···
167
let [headerState, setHeaderState] = useState<"default" | "controls">(
168
"default",
169
);
0
170
return (
171
<DashboardIdContext.Provider value={props.id}>
172
<div
···
186
ref={ref}
187
id="home-content"
188
>
0
0
189
{Object.keys(props.tabs).length <= 1 && !controls ? null : (
190
<>
191
<Header>
···
256
);
257
}
258
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
259
export const HomeDashboardControls = (props: {
260
searchValue: string;
261
setSearchValueAction: (searchValue: string) => void;
···
449
className={`dashboardSearchInput
450
appearance-none! outline-hidden!
451
w-full min-w-0 text-primary relative pl-7 pr-1 -my-px
452
-
border rounded-md border-transparent focus-within:border-border
453
bg-transparent ${props.hasBackgroundImage ? "focus-within:bg-bg-page" : "focus-within:bg-bg-leaflet"} `}
454
type="text"
455
id="pubName"
···
27
import { ExternalLinkTiny } from "components/Icons/ExternalLinkTiny";
28
import { usePreserveScroll } from "src/hooks/usePreserveScroll";
29
import { Tab } from "components/Tab";
30
+
import { PubIcon, PublicationButtons } from "components/ActionBar/Publications";
31
32
export type DashboardState = {
33
display?: "grid" | "list";
···
142
publication?: string;
143
profileDid?: string;
144
actions: React.ReactNode;
145
+
pageTitle?: React.ReactNode;
146
}) {
147
const searchParams = useSearchParams();
148
const tabParam = searchParams.get("tab");
···
169
let [headerState, setHeaderState] = useState<"default" | "controls">(
170
"default",
171
);
172
+
173
return (
174
<DashboardIdContext.Provider value={props.id}>
175
<div
···
189
ref={ref}
190
id="home-content"
191
>
192
+
{props.pageTitle}
193
+
194
{Object.keys(props.tabs).length <= 1 && !controls ? null : (
195
<>
196
<Header>
···
261
);
262
}
263
264
+
export const PageTitle = (props: {
265
+
pageTitle: string;
266
+
controls: React.ReactNode;
267
+
}) => {
268
+
return (
269
+
<MediaContents
270
+
mobile={true}
271
+
className="flex justify-between items-center px-1 mt-1 -mb-1 w-full "
272
+
>
273
+
<h4 className="grow truncate">{props.pageTitle}</h4>
274
+
<div className="shrink-0 h-6">{props.controls}</div>
275
+
</MediaContents>
276
+
);
277
+
};
278
+
279
export const HomeDashboardControls = (props: {
280
searchValue: string;
281
setSearchValueAction: (searchValue: string) => void;
···
469
className={`dashboardSearchInput
470
appearance-none! outline-hidden!
471
w-full min-w-0 text-primary relative pl-7 pr-1 -my-px
472
+
border rounded-md border-border-light focus-within:border-border
473
bg-transparent ${props.hasBackgroundImage ? "focus-within:bg-bg-page" : "focus-within:bg-bg-leaflet"} `}
474
type="text"
475
id="pubName"