@scalemule/news-publication 0.0.39 → 0.0.41

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 CHANGED
@@ -153,3 +153,26 @@ Article integration uses the existing Blog metadata extension:
153
153
  Other modes are `cards` and `features`; optional `area_id` / `feature_ids` select live
154
154
  records. No copied feature snapshot is embedded in a story, so withdrawal takes effect
155
155
  on the next read. Publishers should apply their caption policy to public media copy.
156
+
157
+ ### Votes and moderated conversations
158
+
159
+ Set `config.voting_enabled` and `config.comments_enabled` on the canonical collection
160
+ in the Community Maps editor to enable these controls. Both default to off for
161
+ existing collections. Views share one place record, score, and discussion.
162
+
163
+ The server resolver should include the signed-in reader's `accessToken` from its
164
+ HttpOnly session cookie. Keep the publication API key on the server. Voting
165
+ requires a signed-in reader; guests can read totals and submit comments for review.
166
+ Each reader can upvote (`1`), downvote (`-1`), or clear (`0`) their place vote.
167
+
168
+ The public proxy allows `features/:id/vote` and `comments?feature_id=:id` under
169
+ `collections/:slug`. Without `feature_id`, comments belong to the guide. It strips
170
+ editorial query flags and uses the same signed anonymous identity and origin
171
+ checks as submissions. Comments and replies require the current publication
172
+ permission and always enter review, including comments from signed-in readers.
173
+ Only approved comments whose ancestors remain approved are public.
174
+
175
+ Moderate through **Newsroom → Community Maps → Comments**. Approval requires
176
+ explicit guideline review and the current revision. Existing pumpkin reactions
177
+ remain configurable alongside voting. The UI links to `/account?next=...`; the
178
+ publication must validate that return path and preserve it through sign-in.
@@ -69,11 +69,21 @@ function createPublicationCommunityMapsHandler(options) {
69
69
  const slug = path[1];
70
70
  const rest = path.slice(2);
71
71
  const method = request.method;
72
- const read = method === "GET" && (rest.length === 0 || rest[0] === "features" && (rest.length === 1 || rest.length === 2 && UUID.test(rest[1])) || rest[0] === "media" && rest.length === 2 && UUID.test(rest[1]));
72
+ const discussion = rest.length === 1 && rest[0] === "comments";
73
+ const voting = rest.length === 3 && rest[0] === "features" && UUID.test(rest[1]) && rest[2] === "vote";
74
+ const params = new URL(request.url).searchParams;
75
+ const featureId = params.get("feature_id") || void 0;
76
+ if (discussion && featureId && !UUID.test(featureId))
77
+ return json({ error: { message: "Invalid place" } }, 400);
78
+ const ownList = rest.length === 1 && rest[0] === "submissions";
79
+ const ownUpdate = method === "POST" && rest.length === 2 && rest[0] === "submissions" && UUID.test(rest[1]);
80
+ const ownMedia = rest.length === 4 && rest[0] === "submissions" && UUID.test(rest[1]) && rest[2] === "media" && UUID.test(rest[3]);
81
+ const read = method === "GET" && (ownList || ownMedia || discussion || voting || rest.length === 0 || rest[0] === "features" && (rest.length === 1 || rest.length === 2 && UUID.test(rest[1])) || rest[0] === "media" && rest.length === 2 && UUID.test(rest[1]));
73
82
  const upload = method === "POST" && rest.length === 3 && rest[0] === "submissions" && UUID.test(rest[1]) && rest[2] === "media";
74
83
  const submission = method === "POST" && rest.length === 1 && rest[0] === "submissions";
75
84
  const action = method === "POST" && rest.length === 3 && rest[0] === "features" && UUID.test(rest[1]) && ["reactions", "reports"].includes(rest[2]);
76
- if (!read && !upload && !submission && !action)
85
+ const engagementWrite = method === "POST" && (discussion || voting);
86
+ if (!read && !upload && !submission && !ownUpdate && !action && !engagementWrite)
77
87
  return json({ error: { message: "Route not found" } }, 404);
78
88
  const scope = await options.resolve(request);
79
89
  if (method !== "GET" && request.headers.get("origin") !== new URL(scope.origin).origin)
@@ -97,9 +107,9 @@ function createPublicationCommunityMapsHandler(options) {
97
107
  headers: scope.accessToken ? { authorization: `Bearer ${scope.accessToken}` } : {}
98
108
  };
99
109
  let response;
100
- if (read && rest[0] === "media") {
110
+ if (read && (rest[0] === "media" || ownMedia)) {
101
111
  const upstream = await fetch(
102
- `${gateway.origin}${ROOT}/collections/${slug}/media/${rest[1]}`,
112
+ `${gateway.origin}${ROOT}/collections/${slug}/${ownMedia ? `submissions/${rest[1]}/media/${rest[3]}` : `media/${rest[1]}`}`,
103
113
  {
104
114
  headers: {
105
115
  ...sdk.buildClientContextHeaders(clientContext),
@@ -150,7 +160,34 @@ function createPublicationCommunityMapsHandler(options) {
150
160
  } else {
151
161
  let result;
152
162
  if (read) {
153
- if (!rest.length)
163
+ if (ownList) {
164
+ const after = params.get("after") || void 0;
165
+ if (after && !UUID.test(after))
166
+ throw new ProxyProblem(400, "Invalid submission cursor.");
167
+ result = await sdk$1.communityMaps.listOwnSubmissions(
168
+ slug,
169
+ after,
170
+ callOptions
171
+ );
172
+ } else if (discussion)
173
+ result = await sdk$1.communityMaps.listComments(
174
+ slug,
175
+ {
176
+ feature_id: featureId,
177
+ page: Math.min(
178
+ 1e4,
179
+ Math.max(1, Number(params.get("page")) || 1)
180
+ )
181
+ },
182
+ callOptions
183
+ );
184
+ else if (voting)
185
+ result = await sdk$1.communityMaps.getVote(
186
+ slug,
187
+ rest[1],
188
+ callOptions
189
+ );
190
+ else if (!rest.length)
154
191
  result = await sdk$1.communityMaps.getCollection(slug, callOptions);
155
192
  else if (rest.length === 2)
156
193
  result = await sdk$1.communityMaps.getFeature(
@@ -160,7 +197,7 @@ function createPublicationCommunityMapsHandler(options) {
160
197
  );
161
198
  else {
162
199
  const query = {};
163
- const params = new URL(request.url).searchParams;
200
+ const params2 = new URL(request.url).searchParams;
164
201
  for (const key of [
165
202
  "after",
166
203
  "area_id",
@@ -169,15 +206,15 @@ function createPublicationCommunityMapsHandler(options) {
169
206
  "active_at",
170
207
  "bbox"
171
208
  ]) {
172
- const value = params.get(key);
209
+ const value = params2.get(key);
173
210
  if (value) query[key] = value;
174
211
  }
175
- const limit = params.get("limit");
212
+ const limit = params2.get("limit");
176
213
  if (limit)
177
214
  query.limit = Math.min(200, Math.max(1, Number(limit) || 50));
178
215
  const filters = { ...query };
179
216
  for (const key of ["featured", "verified"]) {
180
- const value = params.get(key);
217
+ const value = params2.get(key);
181
218
  if (value === "true" || value === "false")
182
219
  filters[key] = value === "true";
183
220
  }
@@ -206,7 +243,8 @@ function createPublicationCommunityMapsHandler(options) {
206
243
  caption: String(form.get("caption") || ""),
207
244
  attribution: String(form.get("attribution") || ""),
208
245
  rights_confirmed: form.get("rights_confirmed") === "true",
209
- consent_version: String(form.get("consent_version") || "")
246
+ consent_version: String(form.get("consent_version") || ""),
247
+ ...form.has("idempotency_key") ? { idempotency_key: String(form.get("idempotency_key")) } : {}
210
248
  },
211
249
  callOptions
212
250
  );
@@ -224,7 +262,28 @@ function createPublicationCommunityMapsHandler(options) {
224
262
  }
225
263
  if (!body || typeof body !== "object" || Array.isArray(body))
226
264
  throw new ProxyProblem(400, "Invalid submission.");
227
- if (submission)
265
+ if (discussion)
266
+ result = await sdk$1.communityMaps.createComment(
267
+ slug,
268
+ body,
269
+ featureId,
270
+ callOptions
271
+ );
272
+ else if (voting)
273
+ result = await sdk$1.communityMaps.vote(
274
+ slug,
275
+ rest[1],
276
+ body.value,
277
+ callOptions
278
+ );
279
+ else if (ownUpdate)
280
+ result = await sdk$1.communityMaps.updateOwnSubmission(
281
+ slug,
282
+ rest[1],
283
+ body,
284
+ callOptions
285
+ );
286
+ else if (submission)
228
287
  result = await sdk$1.communityMaps.createSubmission(
229
288
  slug,
230
289
  body,
@@ -67,11 +67,21 @@ function createPublicationCommunityMapsHandler(options) {
67
67
  const slug = path[1];
68
68
  const rest = path.slice(2);
69
69
  const method = request.method;
70
- const read = method === "GET" && (rest.length === 0 || rest[0] === "features" && (rest.length === 1 || rest.length === 2 && UUID.test(rest[1])) || rest[0] === "media" && rest.length === 2 && UUID.test(rest[1]));
70
+ const discussion = rest.length === 1 && rest[0] === "comments";
71
+ const voting = rest.length === 3 && rest[0] === "features" && UUID.test(rest[1]) && rest[2] === "vote";
72
+ const params = new URL(request.url).searchParams;
73
+ const featureId = params.get("feature_id") || void 0;
74
+ if (discussion && featureId && !UUID.test(featureId))
75
+ return json({ error: { message: "Invalid place" } }, 400);
76
+ const ownList = rest.length === 1 && rest[0] === "submissions";
77
+ const ownUpdate = method === "POST" && rest.length === 2 && rest[0] === "submissions" && UUID.test(rest[1]);
78
+ const ownMedia = rest.length === 4 && rest[0] === "submissions" && UUID.test(rest[1]) && rest[2] === "media" && UUID.test(rest[3]);
79
+ const read = method === "GET" && (ownList || ownMedia || discussion || voting || rest.length === 0 || rest[0] === "features" && (rest.length === 1 || rest.length === 2 && UUID.test(rest[1])) || rest[0] === "media" && rest.length === 2 && UUID.test(rest[1]));
71
80
  const upload = method === "POST" && rest.length === 3 && rest[0] === "submissions" && UUID.test(rest[1]) && rest[2] === "media";
72
81
  const submission = method === "POST" && rest.length === 1 && rest[0] === "submissions";
73
82
  const action = method === "POST" && rest.length === 3 && rest[0] === "features" && UUID.test(rest[1]) && ["reactions", "reports"].includes(rest[2]);
74
- if (!read && !upload && !submission && !action)
83
+ const engagementWrite = method === "POST" && (discussion || voting);
84
+ if (!read && !upload && !submission && !ownUpdate && !action && !engagementWrite)
75
85
  return json({ error: { message: "Route not found" } }, 404);
76
86
  const scope = await options.resolve(request);
77
87
  if (method !== "GET" && request.headers.get("origin") !== new URL(scope.origin).origin)
@@ -95,9 +105,9 @@ function createPublicationCommunityMapsHandler(options) {
95
105
  headers: scope.accessToken ? { authorization: `Bearer ${scope.accessToken}` } : {}
96
106
  };
97
107
  let response;
98
- if (read && rest[0] === "media") {
108
+ if (read && (rest[0] === "media" || ownMedia)) {
99
109
  const upstream = await fetch(
100
- `${gateway.origin}${ROOT}/collections/${slug}/media/${rest[1]}`,
110
+ `${gateway.origin}${ROOT}/collections/${slug}/${ownMedia ? `submissions/${rest[1]}/media/${rest[3]}` : `media/${rest[1]}`}`,
101
111
  {
102
112
  headers: {
103
113
  ...buildClientContextHeaders(clientContext),
@@ -148,7 +158,34 @@ function createPublicationCommunityMapsHandler(options) {
148
158
  } else {
149
159
  let result;
150
160
  if (read) {
151
- if (!rest.length)
161
+ if (ownList) {
162
+ const after = params.get("after") || void 0;
163
+ if (after && !UUID.test(after))
164
+ throw new ProxyProblem(400, "Invalid submission cursor.");
165
+ result = await sdk.communityMaps.listOwnSubmissions(
166
+ slug,
167
+ after,
168
+ callOptions
169
+ );
170
+ } else if (discussion)
171
+ result = await sdk.communityMaps.listComments(
172
+ slug,
173
+ {
174
+ feature_id: featureId,
175
+ page: Math.min(
176
+ 1e4,
177
+ Math.max(1, Number(params.get("page")) || 1)
178
+ )
179
+ },
180
+ callOptions
181
+ );
182
+ else if (voting)
183
+ result = await sdk.communityMaps.getVote(
184
+ slug,
185
+ rest[1],
186
+ callOptions
187
+ );
188
+ else if (!rest.length)
152
189
  result = await sdk.communityMaps.getCollection(slug, callOptions);
153
190
  else if (rest.length === 2)
154
191
  result = await sdk.communityMaps.getFeature(
@@ -158,7 +195,7 @@ function createPublicationCommunityMapsHandler(options) {
158
195
  );
159
196
  else {
160
197
  const query = {};
161
- const params = new URL(request.url).searchParams;
198
+ const params2 = new URL(request.url).searchParams;
162
199
  for (const key of [
163
200
  "after",
164
201
  "area_id",
@@ -167,15 +204,15 @@ function createPublicationCommunityMapsHandler(options) {
167
204
  "active_at",
168
205
  "bbox"
169
206
  ]) {
170
- const value = params.get(key);
207
+ const value = params2.get(key);
171
208
  if (value) query[key] = value;
172
209
  }
173
- const limit = params.get("limit");
210
+ const limit = params2.get("limit");
174
211
  if (limit)
175
212
  query.limit = Math.min(200, Math.max(1, Number(limit) || 50));
176
213
  const filters = { ...query };
177
214
  for (const key of ["featured", "verified"]) {
178
- const value = params.get(key);
215
+ const value = params2.get(key);
179
216
  if (value === "true" || value === "false")
180
217
  filters[key] = value === "true";
181
218
  }
@@ -204,7 +241,8 @@ function createPublicationCommunityMapsHandler(options) {
204
241
  caption: String(form.get("caption") || ""),
205
242
  attribution: String(form.get("attribution") || ""),
206
243
  rights_confirmed: form.get("rights_confirmed") === "true",
207
- consent_version: String(form.get("consent_version") || "")
244
+ consent_version: String(form.get("consent_version") || ""),
245
+ ...form.has("idempotency_key") ? { idempotency_key: String(form.get("idempotency_key")) } : {}
208
246
  },
209
247
  callOptions
210
248
  );
@@ -222,7 +260,28 @@ function createPublicationCommunityMapsHandler(options) {
222
260
  }
223
261
  if (!body || typeof body !== "object" || Array.isArray(body))
224
262
  throw new ProxyProblem(400, "Invalid submission.");
225
- if (submission)
263
+ if (discussion)
264
+ result = await sdk.communityMaps.createComment(
265
+ slug,
266
+ body,
267
+ featureId,
268
+ callOptions
269
+ );
270
+ else if (voting)
271
+ result = await sdk.communityMaps.vote(
272
+ slug,
273
+ rest[1],
274
+ body.value,
275
+ callOptions
276
+ );
277
+ else if (ownUpdate)
278
+ result = await sdk.communityMaps.updateOwnSubmission(
279
+ slug,
280
+ rest[1],
281
+ body,
282
+ callOptions
283
+ );
284
+ else if (submission)
226
285
  result = await sdk.communityMaps.createSubmission(
227
286
  slug,
228
287
  body,
@@ -40,53 +40,99 @@ function PublicationCommunityMapPage({
40
40
  () => communityMaps.createCommunityMapsBrowserClient(apiPath),
41
41
  [apiPath]
42
42
  );
43
+ const signInHref = (f) => `/account?next=${encodeURIComponent(`${basePath}/places/${f.id}`)}`;
43
44
  const featureHref = (f) => `${basePath}/places/${f.id}`;
44
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "smcm", children: [
45
- mode !== "guide" && /* @__PURE__ */ jsxRuntime.jsx("p", { children: /* @__PURE__ */ jsxRuntime.jsxs("a", { href: basePath, children: [
46
- "\u2190 ",
47
- collection.config.title
48
- ] }) }),
49
- mode === "submit" ? /* @__PURE__ */ jsxRuntime.jsx(
50
- communityMaps.CommunitySubmissionForm,
51
- {
52
- client,
53
- collection,
54
- initialAreaId: areaId,
55
- correctionFor,
56
- guidelinesHref: "/editorial-standards",
57
- termsHref: "/terms"
58
- }
59
- ) : mode === "feature" && feature ? /* @__PURE__ */ jsxRuntime.jsx(
60
- communityMaps.CommunityFeatureDetail,
61
- {
62
- client,
63
- collection,
64
- feature,
65
- correctionHref: `${basePath}/submit?correction=${feature.id}&area=${feature.area_id}`
66
- }
67
- ) : atlas ? /* @__PURE__ */ jsxRuntime.jsx(
68
- communityMaps.CommunityCityGuide,
69
- {
70
- client,
71
- collection,
72
- atlas,
73
- featureHref,
74
- communityHref: (id) => `${basePath}?area=${id}`,
75
- submitHref: `${basePath}/submit?area=${collection.areas.find((a) => a.slug === atlas.areaSlug)?.id || ""}`
76
- }
77
- ) : /* @__PURE__ */ jsxRuntime.jsx(
78
- communityMaps.CommunityGuide,
79
- {
80
- client,
81
- collection,
82
- featureHref,
83
- communityHref: (a) => `${basePath}/map?area=${a.id}`,
84
- submitHref: `${basePath}/submit${areaId ? `?area=${areaId}` : ""}`,
85
- initialAreaId: areaId,
86
- showMap: mode === "map"
87
- }
88
- )
89
- ] });
45
+ return /* @__PURE__ */ jsxRuntime.jsxs(
46
+ "div",
47
+ {
48
+ className: "smcm",
49
+ style: {
50
+ "--smcm-paper": "var(--pub-paper, Canvas)",
51
+ "--smcm-ink": "var(--pub-ink, CanvasText)",
52
+ "--smcm-border": "var(--pub-rule, currentColor)"
53
+ },
54
+ children: [
55
+ mode !== "guide" && /* @__PURE__ */ jsxRuntime.jsx("p", { children: /* @__PURE__ */ jsxRuntime.jsxs("a", { href: basePath, children: [
56
+ "\u2190 ",
57
+ collection.config.title
58
+ ] }) }),
59
+ mode === "submit" ? correctionFor ? /* @__PURE__ */ jsxRuntime.jsx(
60
+ communityMaps.CommunitySubmissionForm,
61
+ {
62
+ client,
63
+ collection,
64
+ initialAreaId: areaId || collection.areas.find((a) => a.slug === atlas?.areaSlug)?.id,
65
+ correctionFor,
66
+ guidelinesHref: "/editorial-standards",
67
+ termsHref: "/terms"
68
+ }
69
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
70
+ communityMaps.CommunityStreetCapture,
71
+ {
72
+ client,
73
+ collection,
74
+ initialAreaId: areaId || collection.areas.find((a) => a.slug === atlas?.areaSlug)?.id,
75
+ featureHref: (id) => `${basePath}/places/${id}`,
76
+ guidelinesHref: "/editorial-standards",
77
+ termsHref: "/terms"
78
+ }
79
+ ) : mode === "feature" && feature ? /* @__PURE__ */ jsxRuntime.jsx(
80
+ communityMaps.CommunityFeatureDetail,
81
+ {
82
+ client,
83
+ collection,
84
+ feature,
85
+ signInHref: signInHref(feature),
86
+ correctionHref: `${basePath}/submit?correction=${feature.id}&area=${feature.area_id}`
87
+ }
88
+ ) : atlas ? /* @__PURE__ */ jsxRuntime.jsx(
89
+ communityMaps.CommunityCityGuide,
90
+ {
91
+ client,
92
+ collection,
93
+ atlas,
94
+ featureHref,
95
+ signInHref,
96
+ communityHref: (id) => `${basePath}?area=${id}`,
97
+ submitHref: `${basePath}/submit?area=${collection.areas.find((a) => a.slug === atlas.areaSlug)?.id || ""}`
98
+ }
99
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
100
+ communityMaps.CommunityGuide,
101
+ {
102
+ client,
103
+ collection,
104
+ featureHref,
105
+ signInHref,
106
+ communityHref: (a) => `${basePath}/map?area=${a.id}`,
107
+ submitHref: `${basePath}/submit${areaId ? `?area=${areaId}` : ""}`,
108
+ initialAreaId: areaId,
109
+ showMap: mode === "map"
110
+ }
111
+ ),
112
+ (mode === "guide" || mode === "map") && /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "smcm-contribute-links", children: [
113
+ /* @__PURE__ */ jsxRuntime.jsx(
114
+ "a",
115
+ {
116
+ className: "smcm-button",
117
+ href: `${basePath}/submit${areaId ? `?area=${areaId}` : ""}`,
118
+ children: "Take photos and share a street"
119
+ }
120
+ ),
121
+ " ",
122
+ /* @__PURE__ */ jsxRuntime.jsx("a", { href: `${basePath}/submit#my-submissions`, children: "My submissions" })
123
+ ] }),
124
+ (mode === "guide" || mode === "map") && /* @__PURE__ */ jsxRuntime.jsx(
125
+ communityMaps.CommunityDiscussion,
126
+ {
127
+ client,
128
+ collection,
129
+ guidelinesHref: "/editorial-standards",
130
+ termsHref: "/terms"
131
+ }
132
+ )
133
+ ]
134
+ }
135
+ );
90
136
  }
91
137
  function CommunityMapArticleEmbeds({
92
138
  metadata,
@@ -154,6 +200,9 @@ function ArticleMap({
154
200
  communityMaps.CommunityFeatureCard,
155
201
  {
156
202
  feature: f,
203
+ client,
204
+ collection: data.collection,
205
+ signInHref: `/account?next=${encodeURIComponent(`${path}/places/${f.id}`)}`,
157
206
  href: `${path}/places/${f.id}`,
158
207
  photoUrl: f.media[0] ? client.mediaUrl(data.collection.slug, f.media[0].id) : void 0
159
208
  },
@@ -2,7 +2,7 @@
2
2
  import { communityMapEmbeds } from './chunk-IMYBSG7J.js';
3
3
  export { communityMapEmbeds } from './chunk-IMYBSG7J.js';
4
4
  import { useMemo, useState, useEffect } from 'react';
5
- import { createCommunityMapsBrowserClient, CommunitySubmissionForm, CommunityFeatureDetail, CommunityCityGuide, CommunityGuide, CommunityCards, CommunityMap, CommunityFeatureCard } from '@scalemule/react/community-maps';
5
+ import { createCommunityMapsBrowserClient, CommunitySubmissionForm, CommunityStreetCapture, CommunityFeatureDetail, CommunityCityGuide, CommunityGuide, CommunityDiscussion, CommunityCards, CommunityMap, CommunityFeatureCard } from '@scalemule/react/community-maps';
6
6
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
7
7
 
8
8
  function PublicationCommunityMapPage({
@@ -19,53 +19,99 @@ function PublicationCommunityMapPage({
19
19
  () => createCommunityMapsBrowserClient(apiPath),
20
20
  [apiPath]
21
21
  );
22
+ const signInHref = (f) => `/account?next=${encodeURIComponent(`${basePath}/places/${f.id}`)}`;
22
23
  const featureHref = (f) => `${basePath}/places/${f.id}`;
23
- return /* @__PURE__ */ jsxs("div", { className: "smcm", children: [
24
- mode !== "guide" && /* @__PURE__ */ jsx("p", { children: /* @__PURE__ */ jsxs("a", { href: basePath, children: [
25
- "\u2190 ",
26
- collection.config.title
27
- ] }) }),
28
- mode === "submit" ? /* @__PURE__ */ jsx(
29
- CommunitySubmissionForm,
30
- {
31
- client,
32
- collection,
33
- initialAreaId: areaId,
34
- correctionFor,
35
- guidelinesHref: "/editorial-standards",
36
- termsHref: "/terms"
37
- }
38
- ) : mode === "feature" && feature ? /* @__PURE__ */ jsx(
39
- CommunityFeatureDetail,
40
- {
41
- client,
42
- collection,
43
- feature,
44
- correctionHref: `${basePath}/submit?correction=${feature.id}&area=${feature.area_id}`
45
- }
46
- ) : atlas ? /* @__PURE__ */ jsx(
47
- CommunityCityGuide,
48
- {
49
- client,
50
- collection,
51
- atlas,
52
- featureHref,
53
- communityHref: (id) => `${basePath}?area=${id}`,
54
- submitHref: `${basePath}/submit?area=${collection.areas.find((a) => a.slug === atlas.areaSlug)?.id || ""}`
55
- }
56
- ) : /* @__PURE__ */ jsx(
57
- CommunityGuide,
58
- {
59
- client,
60
- collection,
61
- featureHref,
62
- communityHref: (a) => `${basePath}/map?area=${a.id}`,
63
- submitHref: `${basePath}/submit${areaId ? `?area=${areaId}` : ""}`,
64
- initialAreaId: areaId,
65
- showMap: mode === "map"
66
- }
67
- )
68
- ] });
24
+ return /* @__PURE__ */ jsxs(
25
+ "div",
26
+ {
27
+ className: "smcm",
28
+ style: {
29
+ "--smcm-paper": "var(--pub-paper, Canvas)",
30
+ "--smcm-ink": "var(--pub-ink, CanvasText)",
31
+ "--smcm-border": "var(--pub-rule, currentColor)"
32
+ },
33
+ children: [
34
+ mode !== "guide" && /* @__PURE__ */ jsx("p", { children: /* @__PURE__ */ jsxs("a", { href: basePath, children: [
35
+ "\u2190 ",
36
+ collection.config.title
37
+ ] }) }),
38
+ mode === "submit" ? correctionFor ? /* @__PURE__ */ jsx(
39
+ CommunitySubmissionForm,
40
+ {
41
+ client,
42
+ collection,
43
+ initialAreaId: areaId || collection.areas.find((a) => a.slug === atlas?.areaSlug)?.id,
44
+ correctionFor,
45
+ guidelinesHref: "/editorial-standards",
46
+ termsHref: "/terms"
47
+ }
48
+ ) : /* @__PURE__ */ jsx(
49
+ CommunityStreetCapture,
50
+ {
51
+ client,
52
+ collection,
53
+ initialAreaId: areaId || collection.areas.find((a) => a.slug === atlas?.areaSlug)?.id,
54
+ featureHref: (id) => `${basePath}/places/${id}`,
55
+ guidelinesHref: "/editorial-standards",
56
+ termsHref: "/terms"
57
+ }
58
+ ) : mode === "feature" && feature ? /* @__PURE__ */ jsx(
59
+ CommunityFeatureDetail,
60
+ {
61
+ client,
62
+ collection,
63
+ feature,
64
+ signInHref: signInHref(feature),
65
+ correctionHref: `${basePath}/submit?correction=${feature.id}&area=${feature.area_id}`
66
+ }
67
+ ) : atlas ? /* @__PURE__ */ jsx(
68
+ CommunityCityGuide,
69
+ {
70
+ client,
71
+ collection,
72
+ atlas,
73
+ featureHref,
74
+ signInHref,
75
+ communityHref: (id) => `${basePath}?area=${id}`,
76
+ submitHref: `${basePath}/submit?area=${collection.areas.find((a) => a.slug === atlas.areaSlug)?.id || ""}`
77
+ }
78
+ ) : /* @__PURE__ */ jsx(
79
+ CommunityGuide,
80
+ {
81
+ client,
82
+ collection,
83
+ featureHref,
84
+ signInHref,
85
+ communityHref: (a) => `${basePath}/map?area=${a.id}`,
86
+ submitHref: `${basePath}/submit${areaId ? `?area=${areaId}` : ""}`,
87
+ initialAreaId: areaId,
88
+ showMap: mode === "map"
89
+ }
90
+ ),
91
+ (mode === "guide" || mode === "map") && /* @__PURE__ */ jsxs("p", { className: "smcm-contribute-links", children: [
92
+ /* @__PURE__ */ jsx(
93
+ "a",
94
+ {
95
+ className: "smcm-button",
96
+ href: `${basePath}/submit${areaId ? `?area=${areaId}` : ""}`,
97
+ children: "Take photos and share a street"
98
+ }
99
+ ),
100
+ " ",
101
+ /* @__PURE__ */ jsx("a", { href: `${basePath}/submit#my-submissions`, children: "My submissions" })
102
+ ] }),
103
+ (mode === "guide" || mode === "map") && /* @__PURE__ */ jsx(
104
+ CommunityDiscussion,
105
+ {
106
+ client,
107
+ collection,
108
+ guidelinesHref: "/editorial-standards",
109
+ termsHref: "/terms"
110
+ }
111
+ )
112
+ ]
113
+ }
114
+ );
69
115
  }
70
116
  function CommunityMapArticleEmbeds({
71
117
  metadata,
@@ -133,6 +179,9 @@ function ArticleMap({
133
179
  CommunityFeatureCard,
134
180
  {
135
181
  feature: f,
182
+ client,
183
+ collection: data.collection,
184
+ signInHref: `/account?next=${encodeURIComponent(`${path}/places/${f.id}`)}`,
136
185
  href: `${path}/places/${f.id}`,
137
186
  photoUrl: f.media[0] ? client.mediaUrl(data.collection.slug, f.media[0].id) : void 0
138
187
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scalemule/news-publication",
3
- "version": "0.0.39",
3
+ "version": "0.0.41",
4
4
  "description": "Shared server framework for ScaleMule local news publications. Walnut Creek Times is the broadsheet default; each paper supplies its own identity and feature flags.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -109,8 +109,8 @@
109
109
  "story-review.css"
110
110
  ],
111
111
  "peerDependencies": {
112
- "react": ">=18.0.0",
113
- "@scalemule/react": ">=0.0.13 <0.1.0"
112
+ "@scalemule/react": "0.0.18",
113
+ "react": ">=18.0.0"
114
114
  },
115
115
  "peerDependenciesMeta": {
116
116
  "react": {
@@ -129,6 +129,7 @@
129
129
  "prepublishOnly": "node -e \"const v=require('./package.json').version; if(!/^0\\.(0|1)\\.\\d+$/.test(v)){console.error('ERR: SDK '+v+' violates patch-only policy (0.0.x or 0.1.x only). See CONTRIBUTING.md SDK versioning section.');process.exit(1)}\" && npm run lint && npm test && npm run build"
130
130
  },
131
131
  "devDependencies": {
132
+ "@scalemule/react": "0.0.18",
132
133
  "@testing-library/react": "^16.3.3",
133
134
  "@types/node": "^22.15.0",
134
135
  "@types/react": "^19.1.0",
@@ -138,8 +139,7 @@
138
139
  "react-dom": "^19.3.0",
139
140
  "tsup": "^8.5.0",
140
141
  "typescript": "^5.8.3",
141
- "vitest": "^3.2.4",
142
- "@scalemule/react": "^0.0.13"
142
+ "vitest": "^3.2.4"
143
143
  },
144
144
  "keywords": [
145
145
  "scalemule",
@@ -162,7 +162,7 @@
162
162
  },
163
163
  "dependencies": {
164
164
  "@scalemule/compliance": "^0.0.5",
165
- "@scalemule/sdk": "0.0.73",
166
- "@scalemule/react": "0.0.15"
165
+ "@scalemule/react": "0.0.18",
166
+ "@scalemule/sdk": "0.0.75"
167
167
  }
168
168
  }