@scalemule/news-publication 0.0.16 → 0.0.18

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
@@ -72,3 +72,16 @@ The React front page still lives in the Walnut Creek Times repository. This pack
72
72
  ## Versioning
73
73
 
74
74
  Patch only (`0.0.x` or `0.1.x`) until a stable release is explicitly approved. Publishing is manual: `npm publish` from this directory.
75
+
76
+
77
+ ### Reporting credit and sources
78
+
79
+ `ArticleProvenance` renders the public reporting projection: analysis/commentary labels, network and wire credit, and up to five deduplicated source links. It accepts `NewsArticle.metadata` and never renders internal claim mappings, reporter notes or source artifacts. Syndicated commentary keeps its canonical commentary label.
80
+
81
+ ```tsx
82
+ import { ArticleProvenance } from '@scalemule/news-publication'
83
+
84
+ <ArticleProvenance metadata={article.metadata} />
85
+ ```
86
+
87
+ The component inherits the publication's typography. Optional styling hooks are `article-provenance` and `article-provenance__label`. `readerSources()` is available when an existing publication component owns source-list rendering.
@@ -1,10 +1,38 @@
1
- import { recordConsentToCompliance as recordConsentToCompliance$1 } from '@scalemule/compliance';
1
+ import { recordConsentToCompliance as recordConsentToCompliance$1, getCookieConsent, decorateNetworkUrl } from '@scalemule/compliance';
2
2
  export { COOKIE_CONSENT_BACKUP_KEY, COOKIE_CONSENT_EVENT, COOKIE_CONSENT_KEY, COOKIE_CONSENT_MAX_AGE_SECONDS, DEFAULT_CONSENT_HUB_URL, DEFAULT_PREFERENCES_ACCEPTED, DEFAULT_PREFERENCES_REJECTED, EU_EEA_UK_COUNTRIES, NETWORK_CONSENT_STORAGE_KEY, acceptAllCookies, checkUrlConsentBridge, clearCookieConsent, decorateNetworkUrl, getCookieConsent, hasConsented, isCategoryAllowed, isGeoConsentRequired, queryNetworkConsentHub, rejectOptionalCookies, setCookieConsent, setNetworkConsentHub } from '@scalemule/compliance';
3
3
 
4
4
  // src/cookies.ts
5
5
  async function recordConsentToCompliance(record, options) {
6
6
  return recordConsentToCompliance$1(record, options);
7
7
  }
8
+ var delegationInstalled = false;
9
+ function setupNetworkConsentDelegation() {
10
+ if (typeof window === "undefined" || delegationInstalled) return;
11
+ delegationInstalled = true;
12
+ document.addEventListener(
13
+ "click",
14
+ (e) => {
15
+ const target = e.target;
16
+ const anchor = target?.closest("a");
17
+ if (!anchor || !anchor.href) return;
18
+ try {
19
+ const url = new URL(anchor.href, window.location.href);
20
+ if (url.protocol !== "http:" && url.protocol !== "https:") return;
21
+ if (isNetworkHostname(url.hostname)) {
22
+ const consent = getCookieConsent();
23
+ if (consent) {
24
+ const decorated = decorateNetworkUrl(anchor.href);
25
+ if (decorated !== anchor.href) {
26
+ anchor.href = decorated;
27
+ }
28
+ }
29
+ }
30
+ } catch {
31
+ }
32
+ },
33
+ { capture: true }
34
+ );
35
+ }
8
36
  var COOKIE_CATEGORIES = [
9
37
  {
10
38
  id: "essential",
@@ -114,4 +142,121 @@ var COOKIE_CATEGORIES = [
114
142
  }
115
143
  ];
116
144
 
117
- export { COOKIE_CATEGORIES, recordConsentToCompliance };
145
+ // src/network.ts
146
+ var localPublications = [
147
+ {
148
+ slug: "bayareachronicle",
149
+ name: "Bay Area Chronicle",
150
+ url: "https://bayareachronicle.com",
151
+ area: "San Francisco Bay Area",
152
+ description: "The parent home for our local community publications."
153
+ },
154
+ {
155
+ slug: "concordchronicle",
156
+ name: "Concord Chronicle",
157
+ url: "https://concordchronicle.com",
158
+ area: "Concord",
159
+ description: "A new place for Concord\u2019s local stories, independent businesses and community voices."
160
+ },
161
+ {
162
+ slug: "walnutcreektimes",
163
+ name: "Walnut Creek Times",
164
+ url: "https://walnutcreektimes.com",
165
+ area: "Walnut Creek \xB7 Rossmoor \xB7 Saranap \xB7 Pleasant Hill",
166
+ description: "A new place for Walnut Creek\u2019s local stories, independent businesses and community voices."
167
+ },
168
+ {
169
+ slug: "lamorindapost",
170
+ name: "Lamorinda Post",
171
+ url: "https://lamorindapost.com",
172
+ area: "Orinda \xB7 Lafayette \xB7 Moraga",
173
+ description: "A new home for the neighbors, businesses and community stories of Lamorinda."
174
+ },
175
+ {
176
+ slug: "alamoweekly",
177
+ name: "Alamo Weekly",
178
+ url: "https://alamoweekly.com",
179
+ area: "Alamo",
180
+ description: "A new place for Alamo\u2019s local stories, independent businesses and community voices."
181
+ },
182
+ {
183
+ slug: "danvilletimes",
184
+ name: "Danville Times",
185
+ url: "https://danvilletimes.com",
186
+ area: "Danville",
187
+ description: "A new place for Danville\u2019s local stories, independent businesses and community voices."
188
+ },
189
+ {
190
+ slug: "sanramontimes",
191
+ name: "San Ramon Times",
192
+ url: "https://sanramontimes.com",
193
+ area: "San Ramon",
194
+ description: "A new place for San Ramon\u2019s local stories, independent businesses and community voices."
195
+ },
196
+ {
197
+ slug: "diablovalleynews",
198
+ name: "Diablo Valley News",
199
+ url: "https://diablovalleynews.com",
200
+ area: "Concord \xB7 Walnut Creek \xB7 Pleasant Hill \xB7 Alamo \xB7 Danville \xB7 San Ramon",
201
+ description: "Local stories about the people, businesses and community life of the Diablo Valley."
202
+ },
203
+ {
204
+ slug: "trivalleyweekly",
205
+ name: "Tri-Valley Weekly",
206
+ url: "https://trivalleyweekly.com",
207
+ area: "Alamo \xB7 Danville \xB7 San Ramon \xB7 Dublin \xB7 Pleasanton \xB7 Livermore",
208
+ description: "Local stories about the businesses, people and community life of the Tri-Valley."
209
+ },
210
+ {
211
+ slug: "eastbaypost",
212
+ name: "East Bay Post",
213
+ url: "https://eastbaypost.com",
214
+ area: "The East Bay",
215
+ description: "A new publication connecting the people and neighborhoods of the East Bay."
216
+ },
217
+ {
218
+ slug: "napagate",
219
+ name: "Napa Gate",
220
+ url: "https://napagate.com",
221
+ area: "Napa Valley",
222
+ description: "A new local publication for the people, places and everyday life of Napa Valley.",
223
+ listed: false
224
+ }
225
+ ];
226
+ var listedPublications = localPublications.filter(
227
+ (publication) => !("listed" in publication) || publication.listed
228
+ );
229
+ var communityPublications = listedPublications.filter(
230
+ (publication) => publication.slug !== "bayareachronicle"
231
+ );
232
+ function findPublicationBySlug(slug) {
233
+ return localPublications.find((p) => p.slug === slug);
234
+ }
235
+ function findPublicationByHost(host) {
236
+ const cleanHost = host.replace(/^https?:\/\//, "").replace(/\/.*$/, "").toLowerCase();
237
+ return localPublications.find((p) => {
238
+ const pubHost = p.url.replace(/^https?:\/\//, "").replace(/\/.*$/, "").toLowerCase();
239
+ return pubHost === cleanHost || cleanHost.startsWith(`${p.slug}.`);
240
+ });
241
+ }
242
+ function getNetworkPublicationUrl(slug) {
243
+ const pub = findPublicationBySlug(slug);
244
+ if (!pub) return null;
245
+ return decorateNetworkUrl(pub.url + "/");
246
+ }
247
+ var NETWORK_HOSTNAMES = new Set(
248
+ localPublications.map((p) => {
249
+ try {
250
+ return new URL(p.url).hostname.replace(/^www\./, "").toLowerCase();
251
+ } catch {
252
+ return `${p.slug}.com`;
253
+ }
254
+ })
255
+ );
256
+ function isNetworkHostname(hostname) {
257
+ if (!hostname) return false;
258
+ const clean = hostname.replace(/^www\./, "").toLowerCase();
259
+ return NETWORK_HOSTNAMES.has(clean) || clean.endsWith(".bayareachronicle.com") || clean.endsWith(".napsite.com") || clean.endsWith(".site.scalemule.com");
260
+ }
261
+
262
+ export { COOKIE_CATEGORIES, communityPublications, findPublicationByHost, findPublicationBySlug, getNetworkPublicationUrl, isNetworkHostname, listedPublications, localPublications, recordConsentToCompliance, setupNetworkConsentDelegation };
@@ -1,5 +1,5 @@
1
1
  "use client";
2
- import { getCookieConsent, DEFAULT_PREFERENCES_ACCEPTED, COOKIE_CATEGORIES, setCookieConsent, recordConsentToCompliance, DEFAULT_PREFERENCES_REJECTED } from './chunk-QWK6G2UF.js';
2
+ import { getCookieConsent, DEFAULT_PREFERENCES_ACCEPTED, COOKIE_CATEGORIES, setCookieConsent, recordConsentToCompliance, DEFAULT_PREFERENCES_REJECTED } from './chunk-2IYYEFLD.js';
3
3
  import { useState, useEffect } from 'react';
4
4
  import { jsxs, jsx } from 'react/jsx-runtime';
5
5
 
@@ -1,5 +1,5 @@
1
1
  "use client";
2
- import { checkUrlConsentBridge, hasConsented, isGeoConsentRequired, queryNetworkConsentHub, DEFAULT_CONSENT_HUB_URL, acceptAllCookies, rejectOptionalCookies, COOKIE_CONSENT_EVENT, recordConsentToCompliance, setNetworkConsentHub } from './chunk-QWK6G2UF.js';
2
+ import { setupNetworkConsentDelegation, checkUrlConsentBridge, hasConsented, isGeoConsentRequired, queryNetworkConsentHub, DEFAULT_CONSENT_HUB_URL, acceptAllCookies, rejectOptionalCookies, COOKIE_CONSENT_EVENT, recordConsentToCompliance, setNetworkConsentHub } from './chunk-2IYYEFLD.js';
3
3
  import { useState, useEffect } from 'react';
4
4
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
5
5
 
@@ -18,6 +18,7 @@ function CookieConsentBanner({
18
18
  const [visible, setVisible] = useState(false);
19
19
  useEffect(() => {
20
20
  setMounted(true);
21
+ setupNetworkConsentDelegation();
21
22
  if (checkUrlConsentBridge()) {
22
23
  setVisible(false);
23
24
  return;
@@ -1,13 +1,22 @@
1
1
  "use client";
2
- import { listedPublications } from './chunk-5W4EMBFY.js';
3
- import { decorateNetworkUrl } from './chunk-QWK6G2UF.js';
4
- import { useState, useId, useRef } from 'react';
2
+ import { setupNetworkConsentDelegation, COOKIE_CONSENT_EVENT, listedPublications, decorateNetworkUrl } from './chunk-2IYYEFLD.js';
3
+ import { useState, useId, useRef, useEffect } from 'react';
5
4
  import { jsx, jsxs } from 'react/jsx-runtime';
6
5
 
7
6
  function LocalNetworkBar({ currentSlug, className = "", style }) {
8
7
  const [open, setOpen] = useState(false);
8
+ const [, setConsentVersion] = useState(0);
9
9
  const id = useId();
10
10
  const button = useRef(null);
11
+ useEffect(() => {
12
+ setupNetworkConsentDelegation();
13
+ const handleConsent = () => setConsentVersion((v) => v + 1);
14
+ window.addEventListener(COOKIE_CONSENT_EVENT, handleConsent);
15
+ return () => window.removeEventListener(COOKIE_CONSENT_EVENT, handleConsent);
16
+ }, []);
17
+ const prepareLink = (e, pubUrl) => {
18
+ e.currentTarget.href = decorateNetworkUrl(pubUrl + "/");
19
+ };
11
20
  return /* @__PURE__ */ jsx(
12
21
  "nav",
13
22
  {
@@ -45,6 +54,8 @@ function LocalNetworkBar({ currentSlug, className = "", style }) {
45
54
  {
46
55
  href: targetUrl,
47
56
  "aria-current": isCurrent ? "true" : void 0,
57
+ onMouseDown: (e) => !isCurrent && prepareLink(e, publication.url),
58
+ onClick: (e) => !isCurrent && prepareLink(e, publication.url),
48
59
  children: publication.name
49
60
  }
50
61
  ) }, publication.slug);
@@ -5,9 +5,140 @@ var react = require('react');
5
5
  var compliance = require('@scalemule/compliance');
6
6
  var jsxRuntime = require('react/jsx-runtime');
7
7
 
8
+ // src/network.ts
9
+ var localPublications = [
10
+ {
11
+ slug: "bayareachronicle",
12
+ name: "Bay Area Chronicle",
13
+ url: "https://bayareachronicle.com",
14
+ area: "San Francisco Bay Area",
15
+ description: "The parent home for our local community publications."
16
+ },
17
+ {
18
+ slug: "concordchronicle",
19
+ name: "Concord Chronicle",
20
+ url: "https://concordchronicle.com",
21
+ area: "Concord",
22
+ description: "A new place for Concord\u2019s local stories, independent businesses and community voices."
23
+ },
24
+ {
25
+ slug: "walnutcreektimes",
26
+ name: "Walnut Creek Times",
27
+ url: "https://walnutcreektimes.com",
28
+ area: "Walnut Creek \xB7 Rossmoor \xB7 Saranap \xB7 Pleasant Hill",
29
+ description: "A new place for Walnut Creek\u2019s local stories, independent businesses and community voices."
30
+ },
31
+ {
32
+ slug: "lamorindapost",
33
+ name: "Lamorinda Post",
34
+ url: "https://lamorindapost.com",
35
+ area: "Orinda \xB7 Lafayette \xB7 Moraga",
36
+ description: "A new home for the neighbors, businesses and community stories of Lamorinda."
37
+ },
38
+ {
39
+ slug: "alamoweekly",
40
+ name: "Alamo Weekly",
41
+ url: "https://alamoweekly.com",
42
+ area: "Alamo",
43
+ description: "A new place for Alamo\u2019s local stories, independent businesses and community voices."
44
+ },
45
+ {
46
+ slug: "danvilletimes",
47
+ name: "Danville Times",
48
+ url: "https://danvilletimes.com",
49
+ area: "Danville",
50
+ description: "A new place for Danville\u2019s local stories, independent businesses and community voices."
51
+ },
52
+ {
53
+ slug: "sanramontimes",
54
+ name: "San Ramon Times",
55
+ url: "https://sanramontimes.com",
56
+ area: "San Ramon",
57
+ description: "A new place for San Ramon\u2019s local stories, independent businesses and community voices."
58
+ },
59
+ {
60
+ slug: "diablovalleynews",
61
+ name: "Diablo Valley News",
62
+ url: "https://diablovalleynews.com",
63
+ area: "Concord \xB7 Walnut Creek \xB7 Pleasant Hill \xB7 Alamo \xB7 Danville \xB7 San Ramon",
64
+ description: "Local stories about the people, businesses and community life of the Diablo Valley."
65
+ },
66
+ {
67
+ slug: "trivalleyweekly",
68
+ name: "Tri-Valley Weekly",
69
+ url: "https://trivalleyweekly.com",
70
+ area: "Alamo \xB7 Danville \xB7 San Ramon \xB7 Dublin \xB7 Pleasanton \xB7 Livermore",
71
+ description: "Local stories about the businesses, people and community life of the Tri-Valley."
72
+ },
73
+ {
74
+ slug: "eastbaypost",
75
+ name: "East Bay Post",
76
+ url: "https://eastbaypost.com",
77
+ area: "The East Bay",
78
+ description: "A new publication connecting the people and neighborhoods of the East Bay."
79
+ },
80
+ {
81
+ slug: "napagate",
82
+ name: "Napa Gate",
83
+ url: "https://napagate.com",
84
+ area: "Napa Valley",
85
+ description: "A new local publication for the people, places and everyday life of Napa Valley.",
86
+ listed: false
87
+ }
88
+ ];
89
+ var listedPublications = localPublications.filter(
90
+ (publication) => !("listed" in publication) || publication.listed
91
+ );
92
+ listedPublications.filter(
93
+ (publication) => publication.slug !== "bayareachronicle"
94
+ );
95
+ var NETWORK_HOSTNAMES = new Set(
96
+ localPublications.map((p) => {
97
+ try {
98
+ return new URL(p.url).hostname.replace(/^www\./, "").toLowerCase();
99
+ } catch {
100
+ return `${p.slug}.com`;
101
+ }
102
+ })
103
+ );
104
+ function isNetworkHostname(hostname) {
105
+ if (!hostname) return false;
106
+ const clean = hostname.replace(/^www\./, "").toLowerCase();
107
+ return NETWORK_HOSTNAMES.has(clean) || clean.endsWith(".bayareachronicle.com") || clean.endsWith(".napsite.com") || clean.endsWith(".site.scalemule.com");
108
+ }
109
+
110
+ // src/cookies.ts
8
111
  async function recordConsentToCompliance(record, options) {
9
112
  return compliance.recordConsentToCompliance(record, options);
10
113
  }
114
+ var delegationInstalled = false;
115
+ function setupNetworkConsentDelegation() {
116
+ if (typeof window === "undefined" || delegationInstalled) return;
117
+ delegationInstalled = true;
118
+ document.addEventListener(
119
+ "click",
120
+ (e) => {
121
+ const target = e.target;
122
+ const anchor = target?.closest("a");
123
+ if (!anchor || !anchor.href) return;
124
+ try {
125
+ const url = new URL(anchor.href, window.location.href);
126
+ if (url.protocol !== "http:" && url.protocol !== "https:") return;
127
+ if (isNetworkHostname(url.hostname)) {
128
+ const consent = compliance.getCookieConsent();
129
+ if (consent) {
130
+ const decorated = compliance.decorateNetworkUrl(anchor.href);
131
+ if (decorated !== anchor.href) {
132
+ anchor.href = decorated;
133
+ }
134
+ }
135
+ }
136
+ } catch {
137
+ }
138
+ },
139
+ { capture: true }
140
+ );
141
+ }
11
142
  function CookieConsentBanner({
12
143
  title = "Help Us Improve Our Website with Cookies",
13
144
  text,
@@ -23,6 +154,7 @@ function CookieConsentBanner({
23
154
  const [visible, setVisible] = react.useState(false);
24
155
  react.useEffect(() => {
25
156
  setMounted(true);
157
+ setupNetworkConsentDelegation();
26
158
  if (compliance.checkUrlConsentBridge()) {
27
159
  setVisible(false);
28
160
  return;
@@ -1,3 +1,3 @@
1
1
  "use client";
2
- export { CookieConsentBanner } from './chunk-5YQYJ2L2.js';
3
- import './chunk-QWK6G2UF.js';
2
+ export { CookieConsentBanner } from './chunk-HTMIFWNA.js';
3
+ import './chunk-2IYYEFLD.js';
@@ -5,6 +5,104 @@ var react = require('react');
5
5
  var compliance = require('@scalemule/compliance');
6
6
  var jsxRuntime = require('react/jsx-runtime');
7
7
 
8
+ // src/network.ts
9
+ var localPublications = [
10
+ {
11
+ slug: "bayareachronicle",
12
+ name: "Bay Area Chronicle",
13
+ url: "https://bayareachronicle.com",
14
+ area: "San Francisco Bay Area",
15
+ description: "The parent home for our local community publications."
16
+ },
17
+ {
18
+ slug: "concordchronicle",
19
+ name: "Concord Chronicle",
20
+ url: "https://concordchronicle.com",
21
+ area: "Concord",
22
+ description: "A new place for Concord\u2019s local stories, independent businesses and community voices."
23
+ },
24
+ {
25
+ slug: "walnutcreektimes",
26
+ name: "Walnut Creek Times",
27
+ url: "https://walnutcreektimes.com",
28
+ area: "Walnut Creek \xB7 Rossmoor \xB7 Saranap \xB7 Pleasant Hill",
29
+ description: "A new place for Walnut Creek\u2019s local stories, independent businesses and community voices."
30
+ },
31
+ {
32
+ slug: "lamorindapost",
33
+ name: "Lamorinda Post",
34
+ url: "https://lamorindapost.com",
35
+ area: "Orinda \xB7 Lafayette \xB7 Moraga",
36
+ description: "A new home for the neighbors, businesses and community stories of Lamorinda."
37
+ },
38
+ {
39
+ slug: "alamoweekly",
40
+ name: "Alamo Weekly",
41
+ url: "https://alamoweekly.com",
42
+ area: "Alamo",
43
+ description: "A new place for Alamo\u2019s local stories, independent businesses and community voices."
44
+ },
45
+ {
46
+ slug: "danvilletimes",
47
+ name: "Danville Times",
48
+ url: "https://danvilletimes.com",
49
+ area: "Danville",
50
+ description: "A new place for Danville\u2019s local stories, independent businesses and community voices."
51
+ },
52
+ {
53
+ slug: "sanramontimes",
54
+ name: "San Ramon Times",
55
+ url: "https://sanramontimes.com",
56
+ area: "San Ramon",
57
+ description: "A new place for San Ramon\u2019s local stories, independent businesses and community voices."
58
+ },
59
+ {
60
+ slug: "diablovalleynews",
61
+ name: "Diablo Valley News",
62
+ url: "https://diablovalleynews.com",
63
+ area: "Concord \xB7 Walnut Creek \xB7 Pleasant Hill \xB7 Alamo \xB7 Danville \xB7 San Ramon",
64
+ description: "Local stories about the people, businesses and community life of the Diablo Valley."
65
+ },
66
+ {
67
+ slug: "trivalleyweekly",
68
+ name: "Tri-Valley Weekly",
69
+ url: "https://trivalleyweekly.com",
70
+ area: "Alamo \xB7 Danville \xB7 San Ramon \xB7 Dublin \xB7 Pleasanton \xB7 Livermore",
71
+ description: "Local stories about the businesses, people and community life of the Tri-Valley."
72
+ },
73
+ {
74
+ slug: "eastbaypost",
75
+ name: "East Bay Post",
76
+ url: "https://eastbaypost.com",
77
+ area: "The East Bay",
78
+ description: "A new publication connecting the people and neighborhoods of the East Bay."
79
+ },
80
+ {
81
+ slug: "napagate",
82
+ name: "Napa Gate",
83
+ url: "https://napagate.com",
84
+ area: "Napa Valley",
85
+ description: "A new local publication for the people, places and everyday life of Napa Valley.",
86
+ listed: false
87
+ }
88
+ ];
89
+ var listedPublications = localPublications.filter(
90
+ (publication) => !("listed" in publication) || publication.listed
91
+ );
92
+ listedPublications.filter(
93
+ (publication) => publication.slug !== "bayareachronicle"
94
+ );
95
+ new Set(
96
+ localPublications.map((p) => {
97
+ try {
98
+ return new URL(p.url).hostname.replace(/^www\./, "").toLowerCase();
99
+ } catch {
100
+ return `${p.slug}.com`;
101
+ }
102
+ })
103
+ );
104
+
105
+ // src/cookies.ts
8
106
  async function recordConsentToCompliance(record, options) {
9
107
  return compliance.recordConsentToCompliance(record, options);
10
108
  }
@@ -1,3 +1,3 @@
1
1
  "use client";
2
- export { CookieSettingsView } from './chunk-WZ3NQRQO.js';
3
- import './chunk-QWK6G2UF.js';
2
+ export { CookieSettingsView } from './chunk-3MFKPHO3.js';
3
+ import './chunk-2IYYEFLD.js';