@devdogsuga/newsletter 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -8
- package/dist/components.js +55 -17
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/issues.d.ts +12 -17
- package/dist/issues.js +48 -100
- package/dist/theme.d.ts +1 -0
- package/dist/theme.js +2 -1
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -5,8 +5,9 @@ components with two callers:
|
|
|
5
5
|
|
|
6
6
|
- **`apps/platform`** renders issues as pages at `/changelog` and
|
|
7
7
|
`/changelog/<version>`, straight from `ChangelogEmail`.
|
|
8
|
-
- **`@devdogsuga/
|
|
9
|
-
newsletter` — an `.html` preview and an
|
|
8
|
+
- **`@devdogsuga/newsletter-cli`** exports issues as files — `pnpm
|
|
9
|
+
newsletter` from the Backstage root — an `.html` preview and an
|
|
10
|
+
Outlook-importable `.eml` per issue, and pushes or sends them.
|
|
10
11
|
|
|
11
12
|
It deliberately does not go through `@devdogsuga/email`. That package's
|
|
12
13
|
compiled-slot pipeline exists to prove a template cannot branch on its inputs,
|
|
@@ -102,9 +103,10 @@ file; the platform archive and the exported email can never disagree.
|
|
|
102
103
|
## Exporting
|
|
103
104
|
|
|
104
105
|
```bash
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
pnpm
|
|
106
|
+
# rebuilds brand, events and newsletter first, so the export carries the
|
|
107
|
+
# checkout's current issues and club config
|
|
108
|
+
pnpm newsletter # pick issues interactively
|
|
109
|
+
pnpm newsletter '*' --out ~/changelog
|
|
108
110
|
```
|
|
109
111
|
|
|
110
112
|
The `.eml` carries `X-Unsent: 1` and no `Message-ID`, so **classic Outlook
|
|
@@ -117,7 +119,7 @@ does. Either way the draft is a review copy: the send happens with `--send`
|
|
|
117
119
|
## Pushing a draft
|
|
118
120
|
|
|
119
121
|
```bash
|
|
120
|
-
pnpm
|
|
122
|
+
pnpm newsletter 3.0.1 --push
|
|
121
123
|
```
|
|
122
124
|
|
|
123
125
|
appends the issue — same MIME as the `.eml`, minus `X-Unsent` — straight into
|
|
@@ -136,12 +138,12 @@ differently, into Word HTML, with the same result.)
|
|
|
136
138
|
## Sending
|
|
137
139
|
|
|
138
140
|
```bash
|
|
139
|
-
pnpm
|
|
141
|
+
pnpm newsletter 3.0.1 --send listserv@listserv.uga.edu
|
|
140
142
|
```
|
|
141
143
|
|
|
142
144
|
submits the issue over SMTP as the club mailbox, byte-for-byte as authored —
|
|
143
145
|
no composer touches it, so recipients get the document with its stylesheet,
|
|
144
|
-
pins and gradient underlays intact. `--
|
|
146
|
+
pins and gradient underlays intact. `--send` takes the recipients, comma-separated;
|
|
145
147
|
there is deliberately no interactive path to a send.
|
|
146
148
|
|
|
147
149
|
The first `--push` or `--send` opens a browser; sign in as the mailbox and
|
package/dist/components.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { EVENT_TZ, meetingLocation } from "@devdogsuga/brand/event";
|
|
2
3
|
import { MARK_SIZES } from "./assets.js";
|
|
3
4
|
import { bc, brc, darkModeCss, DOT_GRID_CLASS, paintCss, SLANTS_CLASS, tc, } from "./darkmode.js";
|
|
4
5
|
import { blockShadow, chipColors, font, HEADING_TINTS, KIND, PALETTE, SITE, SOCIAL_LINKS, solidBg, FONTS_HREF, UGA, } from "./theme.js";
|
|
@@ -75,14 +76,50 @@ function CtaButton({ label, url, color, ground, fonts, compact = false, }) {
|
|
|
75
76
|
msoLineHeightRule: "exactly",
|
|
76
77
|
}, children: [label, " ", "\u25B8"] }) }) }) }) }));
|
|
77
78
|
}
|
|
79
|
+
/** The chip accent for each kind, from `KIND` so `darkModeCss()` pins it. */
|
|
80
|
+
const KIND_COLOR = {
|
|
81
|
+
"Interest Meeting": KIND.interest,
|
|
82
|
+
"Build Session": KIND.build,
|
|
83
|
+
"Study Session": KIND.study,
|
|
84
|
+
Social: KIND.social,
|
|
85
|
+
"Demo Night": KIND.demo,
|
|
86
|
+
};
|
|
87
|
+
function inEventZone(at, options) {
|
|
88
|
+
return at.toLocaleString("en-US", { timeZone: EVENT_TZ, ...options });
|
|
89
|
+
}
|
|
90
|
+
/** `"6:00 – 7:30 PM"`, or `"11:00 AM – 1:00 PM"` across noon. */
|
|
91
|
+
function timeRange(startsAt, endsAt) {
|
|
92
|
+
const clock = (at) => inEventZone(at, { hour: "numeric", minute: "2-digit" }).split(" ");
|
|
93
|
+
const [start, startMeridiem] = clock(startsAt);
|
|
94
|
+
const [end, endMeridiem] = clock(endsAt);
|
|
95
|
+
return startMeridiem === endMeridiem
|
|
96
|
+
? `${start} – ${end} ${endMeridiem}`
|
|
97
|
+
: `${start} ${startMeridiem} – ${end} ${endMeridiem}`;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* What a card prints for a meeting, in Eastern time. A night with no kind is
|
|
101
|
+
* a workshop night, the same reading the site's calendar gives it.
|
|
102
|
+
*/
|
|
103
|
+
function eventCard(meeting) {
|
|
104
|
+
const startsAt = new Date(meeting.startsAt);
|
|
105
|
+
return {
|
|
106
|
+
chip: meeting.kind ?? "Workshop",
|
|
107
|
+
color: (meeting.kind && KIND_COLOR[meeting.kind]) ?? KIND.workshop,
|
|
108
|
+
dow: inEventZone(startsAt, { weekday: "short" }).toUpperCase(),
|
|
109
|
+
month: inEventZone(startsAt, { month: "short" }),
|
|
110
|
+
day: inEventZone(startsAt, { day: "numeric" }),
|
|
111
|
+
time: timeRange(startsAt, new Date(meeting.endsAt)),
|
|
112
|
+
loc: meetingLocation(meeting.building, meeting.location) ?? "TBA",
|
|
113
|
+
};
|
|
114
|
+
}
|
|
78
115
|
/**
|
|
79
116
|
* An upcoming-event row, drawn the way the site's schedule list draws one: a
|
|
80
117
|
* stacked weekday / day-number date column, a chip pill, a display-face
|
|
81
118
|
* title, and the time + room in quiet sans.
|
|
82
119
|
*/
|
|
83
|
-
function EventRow({
|
|
120
|
+
function EventRow({ meeting, ctx }) {
|
|
84
121
|
const { fonts } = ctx;
|
|
85
|
-
const
|
|
122
|
+
const card = eventCard(meeting);
|
|
86
123
|
return (_jsx("table", { ...TABLE_RESET, width: "100%", bgcolor: PALETTE.card, className: `${bc(PALETTE.card)} ${brc(PALETTE.border)}`, style: {
|
|
87
124
|
border: `1px solid ${PALETTE.border}`,
|
|
88
125
|
borderRadius: "10px",
|
|
@@ -94,29 +131,29 @@ function EventRow({ event, ctx, }) {
|
|
|
94
131
|
letterSpacing: "2px",
|
|
95
132
|
color: PALETTE.mute,
|
|
96
133
|
textTransform: "uppercase",
|
|
97
|
-
}, children:
|
|
134
|
+
}, children: card.dow }), _jsx("div", { className: tc(PALETTE.ink), style: {
|
|
98
135
|
...font(800, 28, 1.1, fonts.display),
|
|
99
136
|
color: PALETTE.ink,
|
|
100
137
|
fontVariantNumeric: "tabular-nums",
|
|
101
138
|
padding: "3px 0 2px",
|
|
102
|
-
}, children: day }), _jsx("div", { className: tc(PALETTE.dim), style: {
|
|
139
|
+
}, children: card.day }), _jsx("div", { className: tc(PALETTE.dim), style: {
|
|
103
140
|
...font(700, 10, 1, fonts.display),
|
|
104
141
|
letterSpacing: "2px",
|
|
105
142
|
color: PALETTE.dim,
|
|
106
143
|
textTransform: "uppercase",
|
|
107
|
-
}, children: month })] }), _jsx(DividerCell, { vertical: true }), _jsx("td", { valign: "middle", style: { padding: "15px 17px" }, children: _jsx("table", { ...TABLE_RESET, width: "100%", children: _jsx("tbody", { children: _jsxs("tr", { children: [_jsxs("td", { valign: "middle", children: [_jsx(Chip, { label:
|
|
144
|
+
}, children: card.month })] }), _jsx(DividerCell, { vertical: true }), _jsx("td", { valign: "middle", style: { padding: "15px 17px" }, children: _jsx("table", { ...TABLE_RESET, width: "100%", children: _jsx("tbody", { children: _jsxs("tr", { children: [_jsxs("td", { valign: "middle", children: [_jsx(Chip, { label: card.chip, color: card.color, fonts: fonts }), _jsx("div", { className: tc(PALETTE.ink), style: {
|
|
108
145
|
...font(800, 18, 1.25, fonts.display),
|
|
109
146
|
color: PALETTE.ink,
|
|
110
147
|
letterSpacing: "-0.2px",
|
|
111
148
|
padding: "9px 0 5px",
|
|
112
|
-
}, children:
|
|
149
|
+
}, children: meeting.title }), _jsxs("div", { className: tc(PALETTE.mute), style: {
|
|
113
150
|
...font(400, 13, 1.5, fonts.sans),
|
|
114
151
|
color: PALETTE.mute,
|
|
115
|
-
}, children: [
|
|
152
|
+
}, children: [card.time, " ", _jsx("span", { className: tc(PALETTE.dim), style: { color: PALETTE.dim }, children: "\u00B7" }), " ", card.loc] }), _jsx("div", { className: tc(PALETTE.dim), style: {
|
|
116
153
|
...font(400, 13, 1.55, fonts.sans),
|
|
117
154
|
color: PALETTE.dim,
|
|
118
155
|
paddingTop: "7px",
|
|
119
|
-
}, children:
|
|
156
|
+
}, children: meeting.summary })] }), meeting.rsvpUrl ? (_jsx("td", { valign: "middle", align: "right", width: 74, style: { paddingLeft: "10px" }, children: _jsx(CtaButton, { label: "RSVP", url: meeting.rsvpUrl, color: card.color, ground: PALETTE.card, fonts: fonts, compact: true }) })) : null] }) }) }) })] }) }) }));
|
|
120
157
|
}
|
|
121
158
|
/** A `## heading` in the terminal voice. */
|
|
122
159
|
function SectionHeading({ children, color, fonts, paddingBottom = 14, }) {
|
|
@@ -140,6 +177,7 @@ function QuietCard({ children }) {
|
|
|
140
177
|
export function ChangelogEmail({ issue, ctx, }) {
|
|
141
178
|
const { fonts, assets } = ctx;
|
|
142
179
|
const featured = issue.featured;
|
|
180
|
+
const featuredCard = eventCard(featured);
|
|
143
181
|
const arrowLinkStyle = {
|
|
144
182
|
color: PALETTE.ink,
|
|
145
183
|
textDecoration: "none",
|
|
@@ -187,11 +225,11 @@ export function ChangelogEmail({ issue, ctx, }) {
|
|
|
187
225
|
...font(400, 15, 1.6, fonts.sans),
|
|
188
226
|
color: PALETTE.mute,
|
|
189
227
|
paddingTop: "11px",
|
|
190
|
-
}, children: issue.intro })] }) }), _jsx("tr", { children: _jsxs("td", { style: { padding: "20px 22px 6px" }, children: [_jsx(SectionHeading, { color:
|
|
191
|
-
border: `1px solid ${
|
|
228
|
+
}, children: issue.intro })] }) }), _jsx("tr", { children: _jsxs("td", { style: { padding: "20px 22px 6px" }, children: [_jsx(SectionHeading, { color: featuredCard.color, fonts: fonts, children: issue.featuredLabel }), _jsx("table", { ...TABLE_RESET, width: "100%", bgcolor: PALETTE.card2, className: `${bc(PALETTE.card2)} ${brc(featuredCard.color)}`, style: {
|
|
229
|
+
border: `1px solid ${featuredCard.color}`,
|
|
192
230
|
borderRadius: "10px",
|
|
193
|
-
...blockShadow(
|
|
194
|
-
}, children: _jsx("tbody", { children: _jsx("tr", { children: _jsxs("td", { style: { padding: "22px 22px 24px" }, children: [_jsx(Chip, { label:
|
|
231
|
+
...blockShadow(featuredCard.color),
|
|
232
|
+
}, children: _jsx("tbody", { children: _jsx("tr", { children: _jsxs("td", { style: { padding: "22px 22px 24px" }, children: [_jsx(Chip, { label: featuredCard.chip, color: featuredCard.color, ground: PALETTE.card2, fonts: fonts }), _jsx("div", { className: tc(PALETTE.ink), style: {
|
|
195
233
|
...font(700, 30, 1.1, fonts.display),
|
|
196
234
|
color: PALETTE.ink,
|
|
197
235
|
letterSpacing: "-0.4px",
|
|
@@ -200,23 +238,23 @@ export function ChangelogEmail({ issue, ctx, }) {
|
|
|
200
238
|
...font(700, 14, 1.5, fonts.sans),
|
|
201
239
|
color: PALETTE.ink,
|
|
202
240
|
paddingRight: "8px",
|
|
203
|
-
}, children: [
|
|
241
|
+
}, children: [featuredCard.dow, ", ", featuredCard.month, " ", featuredCard.day] }), _jsx(DividerCell, { vertical: true }), _jsx("td", { className: tc(PALETTE.mute), style: {
|
|
204
242
|
...font(400, 14, 1.5, fonts.sans),
|
|
205
243
|
color: PALETTE.mute,
|
|
206
244
|
padding: "0 8px",
|
|
207
|
-
}, children:
|
|
245
|
+
}, children: featuredCard.time }), _jsx(DividerCell, { vertical: true }), _jsx("td", { className: tc(PALETTE.mute), style: {
|
|
208
246
|
...font(400, 14, 1.5, fonts.sans),
|
|
209
247
|
color: PALETTE.mute,
|
|
210
248
|
padding: "0 8px",
|
|
211
|
-
}, children:
|
|
249
|
+
}, children: featuredCard.loc })] }) }) }), _jsx("div", { className: tc(PALETTE.mute), style: {
|
|
212
250
|
...font(400, 15, 1.6, fonts.sans),
|
|
213
251
|
color: PALETTE.mute,
|
|
214
252
|
paddingTop: "10px",
|
|
215
|
-
}, children: featured.
|
|
253
|
+
}, children: featured.summary }), _jsx("div", { style: {
|
|
216
254
|
height: "18px",
|
|
217
255
|
lineHeight: "18px",
|
|
218
256
|
fontSize: "0px",
|
|
219
|
-
}, children: " " }), _jsx(CtaButton, { label: issue.cta, url: featured.
|
|
257
|
+
}, children: " " }), _jsx(CtaButton, { label: issue.cta, url: featured.rsvpUrl ?? `${SITE}/events`, color: UGA, fonts: fonts })] }) }) }) })] }) }), _jsx("tr", { children: _jsxs("td", { style: { padding: "22px 22px 0" }, children: [_jsx(SectionHeading, { color: PALETTE.ink, fonts: fonts, children: "upcoming" }), issue.upcoming.map((meeting) => (_jsx(EventRow, { meeting: meeting, ctx: ctx }, meeting.id))), _jsx("div", { style: { padding: "2px 0 4px", textAlign: "center" }, children: _jsx("a", { href: `${SITE}/events`, className: tc(KIND.build), style: {
|
|
220
258
|
...font(700, 13, 1, fonts.mono),
|
|
221
259
|
color: KIND.build,
|
|
222
260
|
textDecoration: "none",
|
package/dist/index.d.ts
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
export { ChangelogDocument, ChangelogEmail } from "./components.js";
|
|
10
10
|
export { paintCss } from "./darkmode.js";
|
|
11
11
|
export { DATA_URI_ASSETS, DEVDOGS_LOCKUP, GDGC_UGA, MARK_SIZES, type NewsletterAssets, type RenderContext, } from "./assets.js";
|
|
12
|
-
export { type
|
|
12
|
+
export { type ChangelogIssue, ISSUES, issueByVersion } from "./issues.js";
|
|
13
13
|
export { SOCIAL_ICON_NAMES, socialIconDataUri, socialIconSvg, type SocialIconName, } from "./icons.js";
|
|
14
14
|
export { CSS_VARIABLE_FONTS, EMAIL_FONTS, FONTS_HREF, type FontStacks, KIND, PALETTE, SITE, SOCIAL_LINKS, UGA, } from "./theme.js";
|
|
15
15
|
import { type RenderContext } from "./assets.js";
|
package/dist/index.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
export { ChangelogDocument, ChangelogEmail } from "./components.js";
|
|
10
10
|
export { paintCss } from "./darkmode.js";
|
|
11
11
|
export { DATA_URI_ASSETS, DEVDOGS_LOCKUP, GDGC_UGA, MARK_SIZES, } from "./assets.js";
|
|
12
|
-
export { ISSUES, issueByVersion
|
|
12
|
+
export { ISSUES, issueByVersion } from "./issues.js";
|
|
13
13
|
export { SOCIAL_ICON_NAMES, socialIconDataUri, socialIconSvg, } from "./icons.js";
|
|
14
14
|
export { CSS_VARIABLE_FONTS, EMAIL_FONTS, FONTS_HREF, KIND, PALETTE, SITE, SOCIAL_LINKS, UGA, } from "./theme.js";
|
|
15
15
|
import { DATA_URI_ASSETS } from "./assets.js";
|
package/dist/issues.d.ts
CHANGED
|
@@ -1,18 +1,13 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
time: string;
|
|
12
|
-
loc: string;
|
|
13
|
-
rsvp: string | null;
|
|
14
|
-
blurb: string;
|
|
15
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* The Changelog's content: the dated sends themselves, listing events read
|
|
3
|
+
* from the club config in `@devdogsuga/events`.
|
|
4
|
+
*
|
|
5
|
+
* Issues are versioned with semver — a send is a release. Issue copy lives
|
|
6
|
+
* here rather than in the components so a copy pass before a send touches one
|
|
7
|
+
* file, and so the platform's archive pages and the exported emails can never
|
|
8
|
+
* disagree about what an issue said.
|
|
9
|
+
*/
|
|
10
|
+
import { type Meeting } from "@devdogsuga/events";
|
|
16
11
|
export interface ChangelogIssue {
|
|
17
12
|
/** Semver, doubling as the URL segment and the export filename. */
|
|
18
13
|
version: string;
|
|
@@ -29,10 +24,10 @@ export interface ChangelogIssue {
|
|
|
29
24
|
intro: string;
|
|
30
25
|
/** The `##` heading over the hero card, e.g. `"happening_today"`. */
|
|
31
26
|
featuredLabel: string;
|
|
32
|
-
featured:
|
|
27
|
+
featured: Meeting;
|
|
33
28
|
/** The hero card's button label. */
|
|
34
29
|
cta: string;
|
|
35
|
-
upcoming:
|
|
30
|
+
upcoming: Meeting[];
|
|
36
31
|
signoff: string;
|
|
37
32
|
}
|
|
38
33
|
export declare const ISSUES: ChangelogIssue[];
|
package/dist/issues.js
CHANGED
|
@@ -1,106 +1,34 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The Changelog's content:
|
|
3
|
-
*
|
|
2
|
+
* The Changelog's content: the dated sends themselves, listing events read
|
|
3
|
+
* from the club config in `@devdogsuga/events`.
|
|
4
4
|
*
|
|
5
|
-
* Issues are versioned with semver — a send is a release.
|
|
5
|
+
* Issues are versioned with semver — a send is a release. Issue copy lives
|
|
6
6
|
* here rather than in the components so a copy pass before a send touches one
|
|
7
7
|
* file, and so the platform's archive pages and the exported emails can never
|
|
8
8
|
* disagree about what an issue said.
|
|
9
9
|
*/
|
|
10
|
-
import {
|
|
11
|
-
const
|
|
10
|
+
import { getClubConfig } from "@devdogsuga/events";
|
|
11
|
+
const MEETINGS = new Map(getClubConfig().meetings.map((m) => [m.id, m]));
|
|
12
|
+
/** A meeting from the club config, by id. Throws on a typo rather than
|
|
13
|
+
* rendering an issue with a hole in it. */
|
|
14
|
+
function meeting(id) {
|
|
15
|
+
const found = MEETINGS.get(id);
|
|
16
|
+
if (found === undefined) {
|
|
17
|
+
throw new Error(`No meeting "${id}" in the club config.`);
|
|
18
|
+
}
|
|
19
|
+
return found;
|
|
20
|
+
}
|
|
21
|
+
/** Every event an issue lists, by its id in the club config. */
|
|
12
22
|
const EVENTS = {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
blurb: "Come meet the new leadership team and get the details on all things DevDogs. Returning? Learn what's changing, what's staying the same, and how to get involved this year. Free food, too.",
|
|
23
|
-
},
|
|
24
|
-
coldstart: {
|
|
25
|
-
chip: "Kickoff",
|
|
26
|
-
color: KIND.workshop,
|
|
27
|
-
title: "Cold Start",
|
|
28
|
-
dow: "MON",
|
|
29
|
-
date: "Sep 14",
|
|
30
|
-
time: "6:00 – 7:30 PM",
|
|
31
|
-
loc: "DLW 124",
|
|
32
|
-
rsvp: "https://uga.campuslabs.com/engage/event/12664196",
|
|
33
|
-
blurb: "The inaugural meeting for the 2026–2027 year. Get set up to contribute to this year's projects. Plus, a collaborative coding workshop: an introduction to Git, GitHub, and how to contribute to a team project.",
|
|
34
|
-
},
|
|
35
|
-
dev1: {
|
|
36
|
-
chip: "Dev Session",
|
|
37
|
-
color: KIND.build,
|
|
38
|
-
title: "Dev Session #1",
|
|
39
|
-
dow: "WED",
|
|
40
|
-
date: "Sep 16",
|
|
41
|
-
time: "6:00 – 7:00 PM",
|
|
42
|
-
loc: "DLW 124",
|
|
43
|
-
rsvp: "https://uga.campuslabs.com/engage/event/12664183",
|
|
44
|
-
blurb: DEV_SESSION_BLURB,
|
|
45
|
-
},
|
|
46
|
-
// The three workshop nights have no summary in events yet — these
|
|
47
|
-
// blurbs are authored from their linked workshop topics; swap in the real
|
|
48
|
-
// copy once it lands in the meetings config.
|
|
49
|
-
nextflutter: {
|
|
50
|
-
chip: "Workshop",
|
|
51
|
-
color: KIND.workshop,
|
|
52
|
-
title: "Workshops: Next.js & Flutter",
|
|
53
|
-
dow: "MON",
|
|
54
|
-
date: "Sep 21",
|
|
55
|
-
time: "6:00 – 7:30 PM",
|
|
56
|
-
loc: "DLW 124",
|
|
57
|
-
rsvp: null,
|
|
58
|
-
blurb: "A framework double-header: build for the web with Next.js and go cross-platform with Flutter.",
|
|
59
|
-
},
|
|
60
|
-
dev2: {
|
|
61
|
-
chip: "Dev Session",
|
|
62
|
-
color: KIND.build,
|
|
63
|
-
title: "Dev Session #2",
|
|
64
|
-
dow: "WED",
|
|
65
|
-
date: "Sep 23",
|
|
66
|
-
time: "6:00 – 7:00 PM",
|
|
67
|
-
loc: "DLW 124",
|
|
68
|
-
rsvp: "https://uga.campuslabs.com/engage/event/12664184",
|
|
69
|
-
blurb: DEV_SESSION_BLURB,
|
|
70
|
-
},
|
|
71
|
-
supabase: {
|
|
72
|
-
chip: "Workshop",
|
|
73
|
-
color: KIND.workshop,
|
|
74
|
-
title: "Workshop: Supabase",
|
|
75
|
-
dow: "MON",
|
|
76
|
-
date: "Sep 28",
|
|
77
|
-
time: "6:00 – 7:30 PM",
|
|
78
|
-
loc: "DLW 124",
|
|
79
|
-
rsvp: null,
|
|
80
|
-
blurb: "Get hands-on with Supabase: Postgres, auth, and realtime data for this year's projects.",
|
|
81
|
-
},
|
|
82
|
-
dev3: {
|
|
83
|
-
chip: "Dev Session",
|
|
84
|
-
color: KIND.build,
|
|
85
|
-
title: "Dev Session #3",
|
|
86
|
-
dow: "WED",
|
|
87
|
-
date: "Sep 30",
|
|
88
|
-
time: "6:00 – 7:00 PM",
|
|
89
|
-
loc: "DLW 124",
|
|
90
|
-
rsvp: "https://uga.campuslabs.com/engage/event/12664184",
|
|
91
|
-
blurb: DEV_SESSION_BLURB,
|
|
92
|
-
},
|
|
93
|
-
career: {
|
|
94
|
-
chip: "Workshop",
|
|
95
|
-
color: KIND.workshop,
|
|
96
|
-
title: "Workshop: Career Fair Readiness",
|
|
97
|
-
dow: "MON",
|
|
98
|
-
date: "Oct 5",
|
|
99
|
-
time: "6:00 – 8:00 PM",
|
|
100
|
-
loc: "DLW 110",
|
|
101
|
-
rsvp: null,
|
|
102
|
-
blurb: "Get ready for the fall career fair: resume polish, portfolio pointers, and how to talk about what you've built.",
|
|
103
|
-
},
|
|
23
|
+
coldstart: meeting("rectaW4iGmfDA3uwQ"),
|
|
24
|
+
build1: meeting("rec1BrdXl7u8bYGXH"),
|
|
25
|
+
nextflutter: meeting("recBF3KxHMKsT4Mz8"),
|
|
26
|
+
build2: meeting("recGqvQqUDFlrXPRc"),
|
|
27
|
+
supabase: meeting("recqDUR1D3CQNBVe5"),
|
|
28
|
+
build3: meeting("rec6aLjA2ZhT45xuh"),
|
|
29
|
+
judging1: meeting("feature-competition-1-judging-2026"),
|
|
30
|
+
career: meeting("recljv0crLDtLIBPc"),
|
|
31
|
+
touchgrass1: meeting("touch-grass-1-2026"),
|
|
104
32
|
};
|
|
105
33
|
export const ISSUES = [
|
|
106
34
|
{
|
|
@@ -116,11 +44,11 @@ export const ISSUES = [
|
|
|
116
44
|
featured: EVENTS.coldstart,
|
|
117
45
|
cta: "RSVP for Cold Start",
|
|
118
46
|
upcoming: [
|
|
119
|
-
EVENTS.
|
|
47
|
+
EVENTS.build1,
|
|
120
48
|
EVENTS.nextflutter,
|
|
121
|
-
EVENTS.
|
|
49
|
+
EVENTS.build2,
|
|
122
50
|
EVENTS.supabase,
|
|
123
|
-
EVENTS.
|
|
51
|
+
EVENTS.build3,
|
|
124
52
|
EVENTS.career,
|
|
125
53
|
],
|
|
126
54
|
signoff: "Doors tonight at 6 in DLW 124. Bring a laptop if you have one, and we'll get you set up to ship either way.",
|
|
@@ -137,9 +65,29 @@ export const ISSUES = [
|
|
|
137
65
|
featuredLabel: "happening_tonight",
|
|
138
66
|
featured: EVENTS.nextflutter,
|
|
139
67
|
cta: "RSVP for Cold Start",
|
|
140
|
-
upcoming: [EVENTS.
|
|
68
|
+
upcoming: [EVENTS.build2, EVENTS.supabase, EVENTS.build3, EVENTS.career],
|
|
141
69
|
signoff: "Doors tonight at 6 in DLW 124. Bring a laptop if you have one, but we'll get you set up to ship either way.",
|
|
142
70
|
},
|
|
71
|
+
{
|
|
72
|
+
version: "3.0.2",
|
|
73
|
+
term: "Fall 2026",
|
|
74
|
+
sendLabel: "Mon · Sep 28",
|
|
75
|
+
command: "changelog --date 2026-09-28",
|
|
76
|
+
title: "Give Your App a Backend Tonight (DevDogs Changelog v3.0.2)",
|
|
77
|
+
preview: "Tonight at 6: the Supabase workshop, then our first feature competition of the year kicks off.",
|
|
78
|
+
tagline: "Your app gets a backend tonight.",
|
|
79
|
+
intro: "Tonight's workshop adds Supabase to the app you started at the Next.js and Flutter workshops: sign-in, a Postgres database, and row-level security. Web and mobile tracks share one project. Then our first feature competition of the year kicks off: form a team of 2 to 4 and build a feature for the club's own platform. Entries close at 6 PM next Monday, when teams present their work.",
|
|
80
|
+
featuredLabel: "happening_tonight",
|
|
81
|
+
featured: EVENTS.supabase,
|
|
82
|
+
cta: "See the schedule",
|
|
83
|
+
upcoming: [
|
|
84
|
+
EVENTS.build3,
|
|
85
|
+
EVENTS.judging1,
|
|
86
|
+
EVENTS.career,
|
|
87
|
+
EVENTS.touchgrass1,
|
|
88
|
+
],
|
|
89
|
+
signoff: "Doors tonight at 6 in DLW 124. Turn on GitHub two-factor before you come: you need it to join a team.",
|
|
90
|
+
},
|
|
143
91
|
];
|
|
144
92
|
export function issueByVersion(version) {
|
|
145
93
|
return ISSUES.find((issue) => issue.version === version);
|
package/dist/theme.d.ts
CHANGED
|
@@ -21,6 +21,7 @@ export declare const KIND: {
|
|
|
21
21
|
readonly build: "#00a6f4";
|
|
22
22
|
readonly study: "#ffb900";
|
|
23
23
|
readonly social: "#ff637e";
|
|
24
|
+
readonly demo: "#7c86ff";
|
|
24
25
|
readonly workshop: "#00d492";
|
|
25
26
|
};
|
|
26
27
|
/** The mauve surfaces, matching the platform (mauve-950 canvas → -300 text). */
|
package/dist/theme.js
CHANGED
|
@@ -9,9 +9,10 @@ export const UGA = "#ba0c2f";
|
|
|
9
9
|
*/
|
|
10
10
|
export const KIND = {
|
|
11
11
|
interest: EVENT_KIND_VISUALS["Interest Meeting"].accent,
|
|
12
|
-
build: EVENT_KIND_VISUALS["
|
|
12
|
+
build: EVENT_KIND_VISUALS["Build Session"].accent,
|
|
13
13
|
study: EVENT_KIND_VISUALS["Study Session"].accent,
|
|
14
14
|
social: EVENT_KIND_VISUALS.Social.accent,
|
|
15
|
+
demo: EVENT_KIND_VISUALS["Demo Night"].accent,
|
|
15
16
|
workshop: EVENT_SEGMENT_VISUALS.workshop.accent,
|
|
16
17
|
};
|
|
17
18
|
/** The mauve surfaces, matching the platform (mauve-950 canvas → -300 text). */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@devdogsuga/newsletter",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "The DevDogs Changelog: email-safe React components rendered as platform pages and exported as Outlook-importable .eml files",
|
|
@@ -30,7 +30,8 @@
|
|
|
30
30
|
"dependencies": {
|
|
31
31
|
"react": "^19.2.8",
|
|
32
32
|
"react-dom": "^19.2.8",
|
|
33
|
-
"@devdogsuga/brand": "0.1.
|
|
33
|
+
"@devdogsuga/brand": "0.1.2",
|
|
34
|
+
"@devdogsuga/events": "0.1.3"
|
|
34
35
|
},
|
|
35
36
|
"devDependencies": {
|
|
36
37
|
"@types/node": "^26.2.0",
|
|
@@ -39,7 +40,7 @@
|
|
|
39
40
|
"@vitest/coverage-v8": "^4.1.11",
|
|
40
41
|
"typescript": "^6.0.3",
|
|
41
42
|
"vitest": "^4.1.11",
|
|
42
|
-
"@devdogsuga/config": "0.1.
|
|
43
|
+
"@devdogsuga/config": "0.1.1"
|
|
43
44
|
},
|
|
44
45
|
"scripts": {
|
|
45
46
|
"build": "tsc",
|