@scalemule/news-publication 0.0.37 → 0.0.39

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.
@@ -104,23 +104,49 @@ function createPublicationCommunityMapsHandler(options) {
104
104
  headers: {
105
105
  ...sdk.buildClientContextHeaders(clientContext),
106
106
  "x-api-key": scope.apiKey,
107
- ...callOptions.headers
107
+ ...callOptions.headers,
108
+ ...request.headers.has("range") ? { range: request.headers.get("range") } : {}
108
109
  },
109
110
  cache: "no-store",
110
111
  redirect: "error",
111
112
  signal: AbortSignal.timeout(3e4)
112
113
  }
113
114
  );
114
- response = upstream.ok ? new Response(upstream.body, {
115
- headers: {
116
- "content-type": "image/png",
117
- "cache-control": "private, no-store",
118
- "x-content-type-options": "nosniff"
119
- }
120
- }) : json(
121
- { error: { message: "Photo unavailable" } },
122
- upstream.status >= 500 ? 503 : upstream.status
123
- );
115
+ const contentType = upstream.headers.get("content-type")?.split(";")[0] || "";
116
+ const allowed = [
117
+ "image/png",
118
+ "video/mp4",
119
+ "video/webm",
120
+ "audio/mpeg",
121
+ "audio/mp4",
122
+ "audio/ogg",
123
+ "audio/wav"
124
+ ].includes(contentType);
125
+ const mediaHeaders = new Headers({
126
+ "cache-control": "private, no-store",
127
+ "x-content-type-options": "nosniff"
128
+ });
129
+ for (const key of [
130
+ "content-range",
131
+ "accept-ranges",
132
+ "content-length"
133
+ ]) {
134
+ const value = upstream.headers.get(key);
135
+ if (value) mediaHeaders.set(key, value);
136
+ }
137
+ if ((upstream.ok || upstream.status === 416) && allowed) {
138
+ mediaHeaders.set("content-type", contentType);
139
+ response = new Response(upstream.body, {
140
+ status: upstream.status,
141
+ headers: mediaHeaders
142
+ });
143
+ } else {
144
+ await upstream.body?.cancel();
145
+ response = json(
146
+ { error: { message: "Media unavailable" } },
147
+ upstream.ok || upstream.status >= 500 ? 503 : upstream.status
148
+ );
149
+ }
124
150
  } else {
125
151
  let result;
126
152
  if (read) {
@@ -164,22 +190,23 @@ function createPublicationCommunityMapsHandler(options) {
164
190
  } else if (upload) {
165
191
  const contentType = request.headers.get("content-type") || "";
166
192
  if (!contentType.startsWith("multipart/form-data;"))
167
- throw new ProxyProblem(415, "Choose a photograph to upload.");
168
- const bytes = await boundedBody(request, 9 * 1024 * 1024);
193
+ throw new ProxyProblem(415, "Choose a photo, video or audio file.");
194
+ const bytes = await boundedBody(request, 26 * 1024 * 1024);
169
195
  const form = await new Response(bytes, {
170
196
  headers: { "content-type": contentType }
171
197
  }).formData();
172
198
  const file = form.get("file");
173
199
  if (!(file instanceof Blob))
174
- throw new ProxyProblem(400, "Photo is required.");
175
- result = await sdk$1.communityMaps.uploadPhoto(
200
+ throw new ProxyProblem(400, "Media is required.");
201
+ result = await sdk$1.communityMaps.uploadMedia(
176
202
  slug,
177
203
  rest[1],
178
204
  file,
179
205
  {
180
206
  caption: String(form.get("caption") || ""),
181
207
  attribution: String(form.get("attribution") || ""),
182
- rights_confirmed: form.get("rights_confirmed") === "true"
208
+ rights_confirmed: form.get("rights_confirmed") === "true",
209
+ consent_version: String(form.get("consent_version") || "")
183
210
  },
184
211
  callOptions
185
212
  );
@@ -102,23 +102,49 @@ function createPublicationCommunityMapsHandler(options) {
102
102
  headers: {
103
103
  ...buildClientContextHeaders(clientContext),
104
104
  "x-api-key": scope.apiKey,
105
- ...callOptions.headers
105
+ ...callOptions.headers,
106
+ ...request.headers.has("range") ? { range: request.headers.get("range") } : {}
106
107
  },
107
108
  cache: "no-store",
108
109
  redirect: "error",
109
110
  signal: AbortSignal.timeout(3e4)
110
111
  }
111
112
  );
112
- response = upstream.ok ? new Response(upstream.body, {
113
- headers: {
114
- "content-type": "image/png",
115
- "cache-control": "private, no-store",
116
- "x-content-type-options": "nosniff"
117
- }
118
- }) : json(
119
- { error: { message: "Photo unavailable" } },
120
- upstream.status >= 500 ? 503 : upstream.status
121
- );
113
+ const contentType = upstream.headers.get("content-type")?.split(";")[0] || "";
114
+ const allowed = [
115
+ "image/png",
116
+ "video/mp4",
117
+ "video/webm",
118
+ "audio/mpeg",
119
+ "audio/mp4",
120
+ "audio/ogg",
121
+ "audio/wav"
122
+ ].includes(contentType);
123
+ const mediaHeaders = new Headers({
124
+ "cache-control": "private, no-store",
125
+ "x-content-type-options": "nosniff"
126
+ });
127
+ for (const key of [
128
+ "content-range",
129
+ "accept-ranges",
130
+ "content-length"
131
+ ]) {
132
+ const value = upstream.headers.get(key);
133
+ if (value) mediaHeaders.set(key, value);
134
+ }
135
+ if ((upstream.ok || upstream.status === 416) && allowed) {
136
+ mediaHeaders.set("content-type", contentType);
137
+ response = new Response(upstream.body, {
138
+ status: upstream.status,
139
+ headers: mediaHeaders
140
+ });
141
+ } else {
142
+ await upstream.body?.cancel();
143
+ response = json(
144
+ { error: { message: "Media unavailable" } },
145
+ upstream.ok || upstream.status >= 500 ? 503 : upstream.status
146
+ );
147
+ }
122
148
  } else {
123
149
  let result;
124
150
  if (read) {
@@ -162,22 +188,23 @@ function createPublicationCommunityMapsHandler(options) {
162
188
  } else if (upload) {
163
189
  const contentType = request.headers.get("content-type") || "";
164
190
  if (!contentType.startsWith("multipart/form-data;"))
165
- throw new ProxyProblem(415, "Choose a photograph to upload.");
166
- const bytes = await boundedBody(request, 9 * 1024 * 1024);
191
+ throw new ProxyProblem(415, "Choose a photo, video or audio file.");
192
+ const bytes = await boundedBody(request, 26 * 1024 * 1024);
167
193
  const form = await new Response(bytes, {
168
194
  headers: { "content-type": contentType }
169
195
  }).formData();
170
196
  const file = form.get("file");
171
197
  if (!(file instanceof Blob))
172
- throw new ProxyProblem(400, "Photo is required.");
173
- result = await sdk.communityMaps.uploadPhoto(
198
+ throw new ProxyProblem(400, "Media is required.");
199
+ result = await sdk.communityMaps.uploadMedia(
174
200
  slug,
175
201
  rest[1],
176
202
  file,
177
203
  {
178
204
  caption: String(form.get("caption") || ""),
179
205
  attribution: String(form.get("attribution") || ""),
180
- rights_confirmed: form.get("rights_confirmed") === "true"
206
+ rights_confirmed: form.get("rights_confirmed") === "true",
207
+ consent_version: String(form.get("consent_version") || "")
181
208
  },
182
209
  callOptions
183
210
  );
@@ -33,7 +33,8 @@ function PublicationCommunityMapPage({
33
33
  areaId,
34
34
  correctionFor,
35
35
  basePath,
36
- apiPath
36
+ apiPath,
37
+ atlas
37
38
  }) {
38
39
  const client = react.useMemo(
39
40
  () => communityMaps.createCommunityMapsBrowserClient(apiPath),
@@ -51,7 +52,9 @@ function PublicationCommunityMapPage({
51
52
  client,
52
53
  collection,
53
54
  initialAreaId: areaId,
54
- correctionFor
55
+ correctionFor,
56
+ guidelinesHref: "/editorial-standards",
57
+ termsHref: "/terms"
55
58
  }
56
59
  ) : mode === "feature" && feature ? /* @__PURE__ */ jsxRuntime.jsx(
57
60
  communityMaps.CommunityFeatureDetail,
@@ -61,6 +64,16 @@ function PublicationCommunityMapPage({
61
64
  feature,
62
65
  correctionHref: `${basePath}/submit?correction=${feature.id}&area=${feature.area_id}`
63
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
+ }
64
77
  ) : /* @__PURE__ */ jsxRuntime.jsx(
65
78
  communityMaps.CommunityGuide,
66
79
  {
@@ -1,5 +1,5 @@
1
1
  import * as react from 'react';
2
- import { CommunityMapCollection, CommunityMapFeature } from '@scalemule/react/community-maps';
2
+ import { CommunityMapCollection, CommunityMapFeature, CommunityCityAtlasConfig } from '@scalemule/react/community-maps';
3
3
  export { c as communityMapEmbeds } from './community-maps-DKcYhQYN.cjs';
4
4
 
5
5
  interface PublicationCommunityMapPageProps {
@@ -10,9 +10,10 @@ interface PublicationCommunityMapPageProps {
10
10
  correctionFor?: string;
11
11
  basePath: string;
12
12
  apiPath?: string;
13
+ atlas?: CommunityCityAtlasConfig;
13
14
  }
14
15
  /** The newspaper supplies its chrome and inherited type. The platform owns map behavior. */
15
- declare function PublicationCommunityMapPage({ collection, feature, mode, areaId, correctionFor, basePath, apiPath, }: PublicationCommunityMapPageProps): react.JSX.Element;
16
+ declare function PublicationCommunityMapPage({ collection, feature, mode, areaId, correctionFor, basePath, apiPath, atlas, }: PublicationCommunityMapPageProps): react.JSX.Element;
16
17
 
17
18
  declare function CommunityMapArticleEmbeds({ metadata, apiPath, }: {
18
19
  metadata: unknown;
@@ -1,5 +1,5 @@
1
1
  import * as react from 'react';
2
- import { CommunityMapCollection, CommunityMapFeature } from '@scalemule/react/community-maps';
2
+ import { CommunityMapCollection, CommunityMapFeature, CommunityCityAtlasConfig } from '@scalemule/react/community-maps';
3
3
  export { c as communityMapEmbeds } from './community-maps-DKcYhQYN.js';
4
4
 
5
5
  interface PublicationCommunityMapPageProps {
@@ -10,9 +10,10 @@ interface PublicationCommunityMapPageProps {
10
10
  correctionFor?: string;
11
11
  basePath: string;
12
12
  apiPath?: string;
13
+ atlas?: CommunityCityAtlasConfig;
13
14
  }
14
15
  /** The newspaper supplies its chrome and inherited type. The platform owns map behavior. */
15
- declare function PublicationCommunityMapPage({ collection, feature, mode, areaId, correctionFor, basePath, apiPath, }: PublicationCommunityMapPageProps): react.JSX.Element;
16
+ declare function PublicationCommunityMapPage({ collection, feature, mode, areaId, correctionFor, basePath, apiPath, atlas, }: PublicationCommunityMapPageProps): react.JSX.Element;
16
17
 
17
18
  declare function CommunityMapArticleEmbeds({ metadata, apiPath, }: {
18
19
  metadata: unknown;
@@ -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, CommunityGuide, CommunityCards, CommunityMap, CommunityFeatureCard } from '@scalemule/react/community-maps';
5
+ import { createCommunityMapsBrowserClient, CommunitySubmissionForm, CommunityFeatureDetail, CommunityCityGuide, CommunityGuide, CommunityCards, CommunityMap, CommunityFeatureCard } from '@scalemule/react/community-maps';
6
6
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
7
7
 
8
8
  function PublicationCommunityMapPage({
@@ -12,7 +12,8 @@ function PublicationCommunityMapPage({
12
12
  areaId,
13
13
  correctionFor,
14
14
  basePath,
15
- apiPath
15
+ apiPath,
16
+ atlas
16
17
  }) {
17
18
  const client = useMemo(
18
19
  () => createCommunityMapsBrowserClient(apiPath),
@@ -30,7 +31,9 @@ function PublicationCommunityMapPage({
30
31
  client,
31
32
  collection,
32
33
  initialAreaId: areaId,
33
- correctionFor
34
+ correctionFor,
35
+ guidelinesHref: "/editorial-standards",
36
+ termsHref: "/terms"
34
37
  }
35
38
  ) : mode === "feature" && feature ? /* @__PURE__ */ jsx(
36
39
  CommunityFeatureDetail,
@@ -40,6 +43,16 @@ function PublicationCommunityMapPage({
40
43
  feature,
41
44
  correctionHref: `${basePath}/submit?correction=${feature.id}&area=${feature.area_id}`
42
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
+ }
43
56
  ) : /* @__PURE__ */ jsx(
44
57
  CommunityGuide,
45
58
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scalemule/news-publication",
3
- "version": "0.0.37",
3
+ "version": "0.0.39",
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",
@@ -162,6 +162,7 @@
162
162
  },
163
163
  "dependencies": {
164
164
  "@scalemule/compliance": "^0.0.5",
165
- "@scalemule/sdk": "^0.0.72"
165
+ "@scalemule/sdk": "0.0.73",
166
+ "@scalemule/react": "0.0.15"
166
167
  }
167
168
  }