tangled
alpha
login
or
join now
leaflet.pub
/
leaflet
289
fork
atom
a tool for shared writing and social publishing
289
fork
atom
overview
issues
28
pulls
pipelines
unify background color for publications
cozylittle.house
10 months ago
4ff9ac2e
de451c11
+181
-80
5 changed files
expand all
collapse all
unified
split
app
[leaflet_id]
page.tsx
lish
[handle]
[publication]
[rkey]
page.tsx
dashboard
PublicationDashboard.tsx
page.tsx
components
ThemeManager
ThemeProvider.tsx
+1
-1
app/[leaflet_id]/page.tsx
···
31
if (!rootEntity || !res.data || res.data.blocked_by_admin)
32
return (
33
<div className="w-screen h-screen flex place-items-center bg-bg-leaflet">
34
-
<div className="bg-bg-page mx-auto p-4 border border-border rounded-md flex flex-col text-center justify-centergap-1 w-fit">
35
<div className="font-bold">
36
Hmmm…we couldn't find that Leaflet.
37
</div>
···
31
if (!rootEntity || !res.data || res.data.blocked_by_admin)
32
return (
33
<div className="w-screen h-screen flex place-items-center bg-bg-leaflet">
34
+
<div className="bg-bg-page mx-auto p-4 border border-border rounded-md flex flex-col text-center justify-center gap-1 w-fit">
35
<div className="font-bold">
36
Hmmm…we couldn't find that Leaflet.
37
</div>
+1
-2
app/lish/[handle]/[publication]/[rkey]/page.tsx
···
1
import Link from "next/link";
2
-
import { Footer } from "../../../Footer";
3
import { getPds, IdResolver } from "@atproto/identity";
4
import { supabaseServerClient } from "supabase/serverClient";
5
import { AtUri } from "@atproto/syntax";
···
59
blocks = firstPage.blocks || [];
60
}
61
return (
62
-
<div className="postPage w-full h-screen bg-bg-leaflet flex items-stretch">
63
<div className="pubWrapper flex flex-col w-full ">
64
<div className="pubContent flex flex-col px-3 sm:px-4 py-3 sm:py-9 mx-auto max-w-prose h-full w-full overflow-auto">
65
<div className="flex flex-col pb-8">
···
1
import Link from "next/link";
0
2
import { getPds, IdResolver } from "@atproto/identity";
3
import { supabaseServerClient } from "supabase/serverClient";
4
import { AtUri } from "@atproto/syntax";
···
58
blocks = firstPage.blocks || [];
59
}
60
return (
61
+
<div className="postPage w-full h-screen bg-[#FDFCFA] flex items-stretch">
62
<div className="pubWrapper flex flex-col w-full ">
63
<div className="pubContent flex flex-col px-3 sm:px-4 py-3 sm:py-9 mx-auto max-w-prose h-full w-full overflow-auto">
64
<div className="flex flex-col pb-8">
+1
-1
app/lish/[handle]/[publication]/dashboard/PublicationDashboard.tsx
···
32
function Tab(props: { name: string; selected: boolean; onSelect: () => void }) {
33
return (
34
<div
35
-
className={`pubTabs border bg-bg-page border-b-0 px-2 pt-1 pb-0.5 rounded-t-md border-border hover:cursor-pointer ${props.selected ? "text-accent-1 font-bold -mb-[1px]" : ""}`}
36
onClick={() => props.onSelect()}
37
>
38
{props.name}
···
32
function Tab(props: { name: string; selected: boolean; onSelect: () => void }) {
33
return (
34
<div
35
+
className={`pubTabs border bg-[#FDFCFA] border-b-0 px-2 pt-1 pb-0.5 rounded-t-md border-border hover:cursor-pointer ${props.selected ? "text-accent-1 font-bold -mb-[1px]" : ""}`}
36
onClick={() => props.onSelect()}
37
>
38
{props.name}
+76
-70
app/lish/[handle]/[publication]/dashboard/page.tsx
···
61
try {
62
return (
63
<ThemeProvider entityID={null}>
64
-
<div className="relative max-w-prose w-full h-full mx-auto flex sm:flex-row flex-col sm:items-stretch sm:px-6">
65
-
<div className="w-12 relative">
66
-
<Sidebar className="mt-6 p-2">
67
-
<Actions publication={publication.uri} />
68
-
</Sidebar>
69
-
</div>
70
-
<div
71
-
className={`h-full overflow-y-scroll pt-4 sm:pl-5 sm:pt-9 w-full`}
72
-
>
73
-
<PublicationDashboard
74
-
name={publication.name}
75
-
tabs={{
76
-
Drafts: (
77
-
<DraftList
78
-
publication={publication.uri}
79
-
drafts={publication.leaflets_in_publications.filter(
80
-
(p) => !p.doc,
81
-
)}
82
-
/>
83
-
),
84
-
Published:
85
-
publication.documents_in_publications.length === 0 ? (
86
-
<div className="italic text-tertiary w-full container text-center place-items-center flex flex-col gap-3 p-3">
87
-
Nothing's been published yet...
88
-
</div>
89
-
) : (
90
-
<div className="publishedList w-full flex flex-col gap-4 pb-6">
91
-
{publication.documents_in_publications.map((doc) => {
92
-
if (!doc.documents) return null;
93
-
let leaflet = publication.leaflets_in_publications.find(
94
-
(l) => doc.documents && l.doc === doc.documents.uri,
95
-
);
96
-
let uri = new AtUri(doc.documents.uri);
97
-
let record = doc.documents
98
-
.data as PubLeafletDocument.Record;
0
0
0
99
100
-
return (
101
-
<React.Fragment key={doc.documents?.uri}>
102
-
<div className="flex w-full ">
103
-
<Link
104
-
href={`/lish/${params.handle}/${params.publication}/${uri.rkey}`}
105
-
className="publishedPost grow flex flex-col gap-2 hover:!no-underline"
106
-
>
107
-
<h3 className="text-primary">{record.title}</h3>
108
-
<p className="italic text-secondary">
109
-
This is a placeholder for description
110
-
</p>
111
-
<p className="text-sm text-tertiary pt-2">
112
-
{record.publishedAt} PlaceholderDate
113
-
</p>
114
-
</Link>
115
-
{leaflet && (
116
<Link
117
-
className="pt-[6px]"
118
-
href={`/${leaflet.leaflet}`}
119
>
120
-
<EditTiny />
0
0
0
0
0
0
0
0
121
</Link>
122
-
)}
123
-
</div>
124
-
<hr className="last:hidden border-border-light" />
125
-
</React.Fragment>
126
-
);
127
-
})}
128
-
</div>
129
-
),
130
-
}}
131
-
defaultTab={"Drafts"}
132
-
/>
0
0
0
0
0
0
0
0
0
0
0
0
0
133
</div>
134
-
<Media mobile>
135
-
<Footer>
136
-
<Actions publication={publication.uri} />
137
-
</Footer>
138
-
</Media>
139
</div>
140
</ThemeProvider>
141
);
···
61
try {
62
return (
63
<ThemeProvider entityID={null}>
64
+
<div className="w-screen h-screen flex place-items-center bg-[#FDFCFA]">
65
+
<div className="relative max-w-prose w-full h-full mx-auto flex sm:flex-row flex-col sm:items-stretch sm:px-6">
66
+
<div className="w-12 relative">
67
+
<Sidebar className="mt-6 p-2">
68
+
<Actions publication={publication.uri} />
69
+
</Sidebar>
70
+
</div>
71
+
<div
72
+
className={`h-full overflow-y-scroll pt-4 sm:pl-5 sm:pt-9 w-full`}
73
+
>
74
+
<PublicationDashboard
75
+
name={publication.name}
76
+
tabs={{
77
+
Drafts: (
78
+
<DraftList
79
+
publication={publication.uri}
80
+
drafts={publication.leaflets_in_publications.filter(
81
+
(p) => !p.doc,
82
+
)}
83
+
/>
84
+
),
85
+
Published:
86
+
publication.documents_in_publications.length === 0 ? (
87
+
<div className="italic text-tertiary w-full container text-center place-items-center flex flex-col gap-3 p-3">
88
+
Nothing's been published yet...
89
+
</div>
90
+
) : (
91
+
<div className="publishedList w-full flex flex-col gap-4 pb-6">
92
+
{publication.documents_in_publications.map((doc) => {
93
+
if (!doc.documents) return null;
94
+
let leaflet =
95
+
publication.leaflets_in_publications.find(
96
+
(l) =>
97
+
doc.documents && l.doc === doc.documents.uri,
98
+
);
99
+
let uri = new AtUri(doc.documents.uri);
100
+
let record = doc.documents
101
+
.data as PubLeafletDocument.Record;
102
103
+
return (
104
+
<React.Fragment key={doc.documents?.uri}>
105
+
<div className="flex w-full ">
0
0
0
0
0
0
0
0
0
0
0
0
0
106
<Link
107
+
href={`/lish/${params.handle}/${params.publication}/${uri.rkey}`}
108
+
className="publishedPost grow flex flex-col hover:!no-underline"
109
>
110
+
<h3 className="text-primary">
111
+
{record.title}
112
+
</h3>
113
+
<p className="italic text-secondary">
114
+
This is a placeholder for description
115
+
</p>
116
+
<p className="text-sm text-tertiary pt-2">
117
+
{record.publishedAt} PlaceholderDate
118
+
</p>
119
</Link>
120
+
{leaflet && (
121
+
<Link
122
+
className="pt-[6px]"
123
+
href={`/${leaflet.leaflet}`}
124
+
>
125
+
<EditTiny />
126
+
</Link>
127
+
)}
128
+
</div>
129
+
<hr className="last:hidden border-border-light" />
130
+
</React.Fragment>
131
+
);
132
+
})}
133
+
</div>
134
+
),
135
+
}}
136
+
defaultTab={"Drafts"}
137
+
/>
138
+
</div>
139
+
<Media mobile>
140
+
<Footer>
141
+
<Actions publication={publication.uri} />
142
+
</Footer>
143
+
</Media>
144
</div>
0
0
0
0
0
145
</div>
146
</ThemeProvider>
147
);
+102
-6
components/ThemeManager/ThemeProvider.tsx
···
5
CSSProperties,
6
useContext,
7
useEffect,
0
8
useState,
9
} from "react";
10
import { colorToString, useColorAttribute } from "./useColorAttribute";
···
12
import { parse, contrastLstar, ColorSpace, sRGB } from "colorjs.io/fn";
13
14
import { useEntity } from "src/replicache";
0
15
16
type CSSVariables = {
17
"--bg-leaflet": string;
···
52
local?: boolean;
53
children: React.ReactNode;
54
}) {
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
0
0
0
0
0
0
0
0
0
0
0
0
0
0
55
let bgLeaflet = useColorAttribute(props.entityID, "theme/page-background");
56
let bgPage = useColorAttribute(props.entityID, "theme/card-background");
57
let primary = useColorAttribute(props.entityID, "theme/primary");
···
70
);
71
})[0];
72
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
0
0
0
0
0
73
useEffect(() => {
74
-
if (props.local) return;
75
let el = document.querySelector(":root") as HTMLElement;
76
if (!el) return;
77
setCSSVariableToColor(el, "--bg-leaflet", bgLeaflet);
···
90
91
//highlight 1 is special because its default value is a calculated value
92
if (highlight1) {
93
-
let color = parseColor(`hsba(${highlight1.data.value})`);
94
el?.style.setProperty(
95
"--highlight-1",
96
`rgb(${colorToString(color, "rgb")})`,
···
112
accentContrast === accent1 ? "1" : "0",
113
);
114
}, [
115
-
props.local,
116
bgLeaflet,
117
bgPage,
118
primary,
···
137
"--accent-contrast": colorToString(accentContrast, "rgb"),
138
"--accent-1-is-contrast": accentContrast === accent1 ? 1 : 0,
139
"--highlight-1": highlight1
140
-
? `rgb(${colorToString(parseColor(`hsba(${highlight1.data.value})`), "rgb")})`
141
: "color-mix(in oklab, rgb(var(--accent-contrast)), rgb(var(--bg-page)) 75%)",
142
"--highlight-2": colorToString(highlight2, "rgb"),
143
"--highlight-3": colorToString(highlight3, "rgb"),
144
} as CSSProperties
145
}
146
>
147
-
{props.children}
0
148
</div>
149
);
150
-
}
151
152
let CardThemeProviderContext = createContext<null | string>(null);
153
export function NestedCardThemeProvider(props: { children: React.ReactNode }) {
···
5
CSSProperties,
6
useContext,
7
useEffect,
8
+
useMemo,
9
useState,
10
} from "react";
11
import { colorToString, useColorAttribute } from "./useColorAttribute";
···
13
import { parse, contrastLstar, ColorSpace, sRGB } from "colorjs.io/fn";
14
15
import { useEntity } from "src/replicache";
16
+
import { useLeafletPublicationData } from "components/PageSWRDataProvider";
17
18
type CSSVariables = {
19
"--bg-leaflet": string;
···
54
local?: boolean;
55
children: React.ReactNode;
56
}) {
57
+
let { data } = useLeafletPublicationData();
58
+
if (!data[0]) return <LeafletThemeProvider {...props} />;
59
+
return <PublicationThemeProvider {...props} />;
60
+
}
61
+
export function PublicationThemeProvider(props: {
62
+
entityID: string | null;
63
+
local?: boolean;
64
+
children: React.ReactNode;
65
+
}) {
66
+
let bgLeaflet = useMemo(() => {
67
+
return parseColor(`#FDFCFA`);
68
+
}, []);
69
+
let bgPage = useColorAttribute(props.entityID, "theme/card-background");
70
+
let primary = useColorAttribute(props.entityID, "theme/primary");
71
+
72
+
let highlight1 = useEntity(props.entityID, "theme/highlight-1");
73
+
let highlight2 = useColorAttribute(props.entityID, "theme/highlight-2");
74
+
let highlight3 = useColorAttribute(props.entityID, "theme/highlight-3");
75
+
76
+
let accent1 = useColorAttribute(props.entityID, "theme/accent-background");
77
+
let accent2 = useColorAttribute(props.entityID, "theme/accent-text");
78
+
// set accent contrast to the accent color that has the highest contrast with the page background
79
+
let accentContrast = [accent1, accent2].sort((a, b) => {
80
+
return (
81
+
getColorContrast(colorToString(b, "rgb"), colorToString(bgPage, "rgb")) -
82
+
getColorContrast(colorToString(a, "rgb"), colorToString(bgPage, "rgb"))
83
+
);
84
+
})[0];
85
+
86
+
return (
87
+
<BaseThemeProvider
88
+
bgLeaflet={bgLeaflet}
89
+
bgPage={bgPage}
90
+
primary={primary}
91
+
highlight2={highlight2}
92
+
highlight3={highlight3}
93
+
highlight1={highlight1?.data.value}
94
+
accent1={accent1}
95
+
accent2={accent2}
96
+
accentContrast={accentContrast}
97
+
>
98
+
{props.children}
99
+
</BaseThemeProvider>
100
+
);
101
+
}
102
+
103
+
export function LeafletThemeProvider(props: {
104
+
entityID: string | null;
105
+
local?: boolean;
106
+
children: React.ReactNode;
107
+
}) {
108
let bgLeaflet = useColorAttribute(props.entityID, "theme/page-background");
109
let bgPage = useColorAttribute(props.entityID, "theme/card-background");
110
let primary = useColorAttribute(props.entityID, "theme/primary");
···
123
);
124
})[0];
125
126
+
return (
127
+
<BaseThemeProvider
128
+
bgLeaflet={bgLeaflet}
129
+
bgPage={bgPage}
130
+
primary={primary}
131
+
highlight2={highlight2}
132
+
highlight3={highlight3}
133
+
highlight1={highlight1?.data.value}
134
+
accent1={accent1}
135
+
accent2={accent2}
136
+
accentContrast={accentContrast}
137
+
>
138
+
{props.children}
139
+
</BaseThemeProvider>
140
+
);
141
+
}
142
+
143
+
let BaseThemeProvider = ({
144
+
local,
145
+
bgLeaflet,
146
+
bgPage,
147
+
primary,
148
+
accent1,
149
+
accent2,
150
+
accentContrast,
151
+
highlight1,
152
+
highlight2,
153
+
highlight3,
154
+
children,
155
+
}: {
156
+
local?: boolean;
157
+
bgLeaflet: AriaColor;
158
+
bgPage: AriaColor;
159
+
primary: AriaColor;
160
+
accent1: AriaColor;
161
+
accent2: AriaColor;
162
+
accentContrast: AriaColor;
163
+
highlight1?: string;
164
+
highlight2: AriaColor;
165
+
highlight3: AriaColor;
166
+
children: React.ReactNode;
167
+
}) => {
168
useEffect(() => {
169
+
if (local) return;
170
let el = document.querySelector(":root") as HTMLElement;
171
if (!el) return;
172
setCSSVariableToColor(el, "--bg-leaflet", bgLeaflet);
···
185
186
//highlight 1 is special because its default value is a calculated value
187
if (highlight1) {
188
+
let color = parseColor(`hsba(${highlight1})`);
189
el?.style.setProperty(
190
"--highlight-1",
191
`rgb(${colorToString(color, "rgb")})`,
···
207
accentContrast === accent1 ? "1" : "0",
208
);
209
}, [
210
+
local,
211
bgLeaflet,
212
bgPage,
213
primary,
···
232
"--accent-contrast": colorToString(accentContrast, "rgb"),
233
"--accent-1-is-contrast": accentContrast === accent1 ? 1 : 0,
234
"--highlight-1": highlight1
235
+
? `rgb(${colorToString(parseColor(`hsba(${highlight1})`), "rgb")})`
236
: "color-mix(in oklab, rgb(var(--accent-contrast)), rgb(var(--bg-page)) 75%)",
237
"--highlight-2": colorToString(highlight2, "rgb"),
238
"--highlight-3": colorToString(highlight3, "rgb"),
239
} as CSSProperties
240
}
241
>
242
+
{" "}
243
+
{children}{" "}
244
</div>
245
);
246
+
};
247
248
let CardThemeProviderContext = createContext<null | string>(null);
249
export function NestedCardThemeProvider(props: { children: React.ReactNode }) {