@mindful-web/marko-web-digital-edition 1.95.0
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/.eslintignore +1 -0
- package/LICENSE +21 -0
- package/README.md +121 -0
- package/components/ad.marko +81 -0
- package/components/ad.marko.js +102 -0
- package/components/article-body.marko +134 -0
- package/components/article-body.marko.js +186 -0
- package/components/author.marko +30 -0
- package/components/author.marko.js +59 -0
- package/components/gate.marko +158 -0
- package/components/gate.marko.js +147 -0
- package/components/ld-json.marko +5 -0
- package/components/ld-json.marko.js +29 -0
- package/components/marko.json +78 -0
- package/components/reflow.marko +67 -0
- package/components/reflow.marko.js +106 -0
- package/components/replica.marko +86 -0
- package/components/replica.marko.js +128 -0
- package/components/scroll.marko +134 -0
- package/components/scroll.marko.js +23 -0
- package/components/theme.marko +73 -0
- package/components/theme.marko.js +30 -0
- package/components/toc.marko +28 -0
- package/components/toc.marko.js +53 -0
- package/components/toolbar.marko +47 -0
- package/components/toolbar.marko.js +54 -0
- package/components/tracking.marko +211 -0
- package/components/tracking.marko.js +60 -0
- package/lib/authors.js +125 -0
- package/lib/entities.js +97 -0
- package/lib/index.js +453 -0
- package/marko.json +6 -0
- package/package.json +33 -0
- package/routes/index.js +129 -0
- package/scss/index.scss +679 -0
- package/templates/article.marko +108 -0
- package/templates/article.marko.js +238 -0
- package/templates/hub.marko +94 -0
- package/templates/hub.marko.js +219 -0
- package/templates/partial.marko +17 -0
- package/templates/partial.marko.js +44 -0
package/.eslintignore
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
*.marko.js
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Parameter1 LLC
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# @mindful-web/marko-web-digital-edition
|
|
2
|
+
|
|
3
|
+
A native digital edition for a magazine issue: a **reflow** article reader and a
|
|
4
|
+
**replica** page view, served on the publisher's own site under the issue's URL,
|
|
5
|
+
gated with IdentityX, tracked with p1events, indexable with paywalled-content
|
|
6
|
+
structured data.
|
|
7
|
+
|
|
8
|
+
The edition is described entirely by a **manifest JSON** produced by the
|
|
9
|
+
digital-edition ingest pipeline from the issue's press PDF. This package reads that
|
|
10
|
+
file; it reads and writes no CMS fields. Page images and ad crops are imgix URLs built
|
|
11
|
+
against the PDF already attached to the magazine issue.
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
```js
|
|
16
|
+
// packages/global/routes/index.js
|
|
17
|
+
const digitalEdition = require('@mindful-web/marko-web-digital-edition/routes');
|
|
18
|
+
|
|
19
|
+
module.exports = (app, siteConfig) => {
|
|
20
|
+
// Mount BEFORE the magazine routes.
|
|
21
|
+
digitalEdition(app, {
|
|
22
|
+
configDir: path.resolve(__dirname, '../config/editions'),
|
|
23
|
+
// Optional: render bylines with the site's own author node.
|
|
24
|
+
authorNode: require('@mindful-web/marko-web-theme-monorail/components/nodes/author.marko'),
|
|
25
|
+
});
|
|
26
|
+
magazine(app);
|
|
27
|
+
// …
|
|
28
|
+
};
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
```scss
|
|
32
|
+
// packages/global/scss/core.scss
|
|
33
|
+
@import "@mindful-web/marko-web-digital-edition/scss/index";
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Drop each issue's manifest at `config/editions/<issueId>.json`. The manifest's `siteId`
|
|
37
|
+
must match the running site's id, or the routes 404 — a shared package ships to every
|
|
38
|
+
site in a repo, and only the issue's own site may serve it.
|
|
39
|
+
|
|
40
|
+
## Options
|
|
41
|
+
|
|
42
|
+
| Option | Default | Purpose |
|
|
43
|
+
|---|---|---|
|
|
44
|
+
| `configDir` | required | Directory holding `<issueId>.json` manifests |
|
|
45
|
+
| `mountPoint` | `/magazine` | Issue URL prefix: `<mountPoint>/<issueId>/digital-edition` |
|
|
46
|
+
| `siteId` | `() => app.locals.config.website('id')` | Site guard |
|
|
47
|
+
| `noIndexKey` | `digitalEdition.native.noIndex` | Site-config path; truthy = robots noindex on every edition page (review hold) |
|
|
48
|
+
| `authorNode` | none | Compiled Marko template rendering `{ author }`; without it bylines are plain linked names |
|
|
49
|
+
| `templates` | `{}` | Overrides for `hub`, `article`, `partial` |
|
|
50
|
+
| `legacyRedirect` | `false` | 301 `/edition/:issueId` to the hub |
|
|
51
|
+
|
|
52
|
+
## URLs
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
<mountPoint>/32023/digital-edition hub: cover, contents, first article
|
|
56
|
+
<mountPoint>/32023/digital-edition?view=print replica page view
|
|
57
|
+
<mountPoint>/32023/digital-edition/<slug> one article, server-rendered
|
|
58
|
+
<mountPoint>/32023/digital-edition/<slug>?partial=1 fragment for the scroll reader
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Sign-in gate
|
|
62
|
+
|
|
63
|
+
Anonymous readers get the same IdentityX flow as the content meter bar and hard-gated
|
|
64
|
+
articles, with the source **`digitalEdition`** so conversions are attributable: the
|
|
65
|
+
email field is visible in the gate itself; a new user is then asked for the site's
|
|
66
|
+
`requiredCreateFieldRows`; an existing user receives a login link. A logged-in user
|
|
67
|
+
still missing required fields sees the profile form instead.
|
|
68
|
+
|
|
69
|
+
- **Hub / continuous reader**: a fixed, collapsible bottom bar
|
|
70
|
+
(`<marko-web-digital-edition-gate variant="bar">`).
|
|
71
|
+
- **Single article page**: an inline panel after the teaser
|
|
72
|
+
(`variant="inline"`).
|
|
73
|
+
|
|
74
|
+
Events carry `contentGateType: "digitalEdition"`, the issue id, publication and issue
|
|
75
|
+
name, plus the article slug and print page on article pages. The IdentityX login form
|
|
76
|
+
needs `req.identityX`, so the site must have `marko-web-identity-x` configured.
|
|
77
|
+
|
|
78
|
+
## Tracking
|
|
79
|
+
|
|
80
|
+
Everything goes through the site's p1events queue (so it carries the reader identity) and
|
|
81
|
+
is gated on `p1events.enabled`. Entities are built server-side in `lib/entities.js` and
|
|
82
|
+
stamped on the elements as `data-edition-entity`; namespaces come from the p1events
|
|
83
|
+
`create-namespace` util (the tenant), never a literal.
|
|
84
|
+
|
|
85
|
+
| Entity | Namespace | Id | Refs |
|
|
86
|
+
|---|---|---|---|
|
|
87
|
+
| issue | `base.<tenant>.magazine-issue` | `<issueId>` | |
|
|
88
|
+
| article | `base.<tenant>.digital-edition-article` | `<issueId>.<slug>` | issue, `content` (the matched web article), `authors` |
|
|
89
|
+
| ad | `base.<tenant>.digital-edition-ad` | `<issueId>.<adId>` | issue, `company` (directory match) |
|
|
90
|
+
|
|
91
|
+
| Category | Action | When | Entity |
|
|
92
|
+
|---|---|---|---|
|
|
93
|
+
| Digital Edition | View | hub, replica and article page loads (`props.view`) | issue |
|
|
94
|
+
| Digital Edition | Page View | replica page 50% visible, once per page | issue |
|
|
95
|
+
| Digital Edition | Engaged View | first human gesture on the hub / replica | issue |
|
|
96
|
+
| Digital Edition Article | View | article page load, and each article entering the scroll reader | article |
|
|
97
|
+
| Digital Edition Article | Scroll | 25 / 50 / 75 / 90 percent of the article's own body, **full body only** (never a teaser) | article |
|
|
98
|
+
| Digital Edition Article | Engaged View | first human gesture on an article page | article |
|
|
99
|
+
| Digital Edition Article | Continue Reading | a teaser or its link is clicked | article |
|
|
100
|
+
| Digital Edition Ad Unit | Impression | unit ≥ 25% in view (served) | ad |
|
|
101
|
+
| Digital Edition Ad Unit | Active View | IAB: ≥ 50% in view for one continuous second (≥ 30% for units over 242,500 px²) | ad |
|
|
102
|
+
| Digital Edition Ad Unit | Click | inline, trailing and replica hotspot links (p1events link listener) | ad |
|
|
103
|
+
|
|
104
|
+
Ad categories mirror the Mindful ad server's so a later swap to real creatives is a rename.
|
|
105
|
+
|
|
106
|
+
## Review hold
|
|
107
|
+
|
|
108
|
+
Set the site-config key (default `digitalEdition.native.noIndex`) to `true` while an
|
|
109
|
+
issue is under review. Every edition page then emits a single
|
|
110
|
+
`<meta name="robots" content="noindex">` and the hub's structured data omits nothing
|
|
111
|
+
else. Flip it to `false` to let search in. Individual articles the ingest marked
|
|
112
|
+
`noindex: true` stay held regardless.
|
|
113
|
+
|
|
114
|
+
## Notes
|
|
115
|
+
|
|
116
|
+
- The page templates render the site document directly with
|
|
117
|
+
`marko-web-default-page-metadata` rather than through a theme page component, because
|
|
118
|
+
`no-index` must reach the metadata component, which owns the single canonical and
|
|
119
|
+
robots tags. Never hand-roll either tag in an override template.
|
|
120
|
+
- Articles need a non-empty reflow body to receive a URL (`isPublishable`).
|
|
121
|
+
- The SCSS consumes no theme variables.
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { cropUrl, cropSrcset } from "../lib";
|
|
2
|
+
import { adEntity, entityAttr } from "../lib/entities";
|
|
3
|
+
|
|
4
|
+
$ const { manifest, item, eager, inline } = input;
|
|
5
|
+
$ const entity = adEntity(manifest, item, { placement: inline ? "inline" : "after" });
|
|
6
|
+
$ const page = item.printPage;
|
|
7
|
+
$ const src = cropUrl(manifest, page, item.bbox, { w: inline ? 640 : 1200 });
|
|
8
|
+
$ const srcset = cropSrcset(manifest, page, item.bbox, inline ? [320, 480, 640, 800] : undefined);
|
|
9
|
+
$ const sizes = inline ? "(min-width: 75rem) 352px, (min-width: 48rem) 288px, (min-width: 34rem) 46vw, calc(100vw - 2rem)" : "(min-width: 768px) 700px, 100vw";
|
|
10
|
+
$ const alt = item.advertiserName ? `Advertisement: ${item.advertiserName}` : "Advertisement";
|
|
11
|
+
$ const label = item.kind === "advertorial" ? "Sponsored" : "Advertisement";
|
|
12
|
+
$ const linked = Boolean(item.landingUrl);
|
|
13
|
+
$ // A trailing ad narrower than a page in print (a directory strip, a fractional
|
|
14
|
+
$ // unit) renders at roughly print scale instead of stretching to the column.
|
|
15
|
+
$ const scaled = !inline && item.bbox && item.bbox[2] < 0.9 ? `max-width: min(100%, ${Math.round(item.bbox[2] * 44)}rem);` : null;
|
|
16
|
+
|
|
17
|
+
<!--
|
|
18
|
+
Print ad rendered as an imgix crop of the issue PDF.
|
|
19
|
+
|
|
20
|
+
Tracking contract: these attributes mirror the Mindful ad server's DOM shape
|
|
21
|
+
(data-unit-form / data-creative-id) so that when this graduates to real
|
|
22
|
+
ADVERTISING_CREATIVE records served through mindful-reporting/apis/advertising,
|
|
23
|
+
the browser tracker swaps over without a redesign.
|
|
24
|
+
|
|
25
|
+
data-p1-* is required because the p1events engagement tracker deliberately ignores
|
|
26
|
+
ad containers, and these ads are same-origin (not framed) so clicks never bubble
|
|
27
|
+
through an iframe boundary the way GAM's do.
|
|
28
|
+
|
|
29
|
+
`inline` floats the unit into the article copy at its print-adjacent position
|
|
30
|
+
(see edition/index.js inlineAdPlacements); the impression tracker reads
|
|
31
|
+
data-edition-placement to tell the two treatments apart.
|
|
32
|
+
-->
|
|
33
|
+
<div
|
|
34
|
+
class=`edition-ad edition-ad--${item.kind}${inline ? " edition-ad--inline" : ""}`
|
|
35
|
+
style=scaled
|
|
36
|
+
data-unit-form="DIGITAL_EDITION"
|
|
37
|
+
data-edition-ad-id=item.id
|
|
38
|
+
data-edition-issue-id=manifest.issueId
|
|
39
|
+
data-edition-page=page
|
|
40
|
+
data-edition-placement=(inline ? "inline" : "after")
|
|
41
|
+
data-advertiser=(item.advertiserName || "")
|
|
42
|
+
data-edition-entity=JSON.stringify(entity)
|
|
43
|
+
data-p1-category="Digital Edition Ad Unit"
|
|
44
|
+
data-p1-entity=entityAttr(entity)
|
|
45
|
+
>
|
|
46
|
+
<span class="edition-ad__label">${label}</span>
|
|
47
|
+
<if(linked)>
|
|
48
|
+
<a
|
|
49
|
+
class="edition-ad__link"
|
|
50
|
+
href=item.landingUrl
|
|
51
|
+
target="_blank"
|
|
52
|
+
rel="nofollow noopener sponsored"
|
|
53
|
+
data-p1-action="Click"
|
|
54
|
+
data-p1-label=(item.advertiserName || "")
|
|
55
|
+
>
|
|
56
|
+
<img
|
|
57
|
+
class="edition-ad__img"
|
|
58
|
+
src=src
|
|
59
|
+
srcset=srcset
|
|
60
|
+
sizes=sizes
|
|
61
|
+
alt=alt
|
|
62
|
+
loading=(eager ? "eager" : "lazy")
|
|
63
|
+
decoding="async"
|
|
64
|
+
/>
|
|
65
|
+
</a>
|
|
66
|
+
</if>
|
|
67
|
+
<else>
|
|
68
|
+
<img
|
|
69
|
+
class="edition-ad__img"
|
|
70
|
+
src=src
|
|
71
|
+
srcset=srcset
|
|
72
|
+
sizes=sizes
|
|
73
|
+
alt=alt
|
|
74
|
+
loading=(eager ? "eager" : "lazy")
|
|
75
|
+
decoding="async"
|
|
76
|
+
/>
|
|
77
|
+
</else>
|
|
78
|
+
<if(item.advertiserName)>
|
|
79
|
+
<span class="edition-ad__advertiser">${item.advertiserName}</span>
|
|
80
|
+
</if>
|
|
81
|
+
</div>
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
// Compiled using marko@4.20.2 - DO NOT EDIT
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
var marko_template = module.exports = require("marko/dist/html").t(__filename),
|
|
5
|
+
marko_componentType = "/@mindful-web/marko-web-digital-edition$1.95.0/components/ad.marko",
|
|
6
|
+
marko_renderer = require("marko/dist/runtime/components/renderer"),
|
|
7
|
+
module_lib_module = require("../lib"),
|
|
8
|
+
lib_module = module_lib_module.default || module_lib_module,
|
|
9
|
+
cropUrl = module_lib_module.cropUrl,
|
|
10
|
+
cropSrcset = module_lib_module.cropSrcset,
|
|
11
|
+
module_entities_module = require("../lib/entities"),
|
|
12
|
+
entities_module = module_entities_module.default || module_entities_module,
|
|
13
|
+
adEntity = module_entities_module.adEntity,
|
|
14
|
+
entityAttr = module_entities_module.entityAttr,
|
|
15
|
+
helpers_escape_xml = require("marko/dist/runtime/html/helpers/escape-xml"),
|
|
16
|
+
marko_escapeXml = helpers_escape_xml.x,
|
|
17
|
+
marko_attr = require("marko/dist/runtime/html/helpers/attr"),
|
|
18
|
+
marko_classAttr = require("marko/dist/runtime/html/helpers/class-attr"),
|
|
19
|
+
marko_styleAttr = require("marko/dist/runtime/html/helpers/style-attr");
|
|
20
|
+
|
|
21
|
+
function render(input, out, __component, component, state) {
|
|
22
|
+
var data = input;
|
|
23
|
+
|
|
24
|
+
const { manifest, item, eager, inline } = input;
|
|
25
|
+
|
|
26
|
+
const entity = adEntity(manifest, item, { placement: inline ? "inline" : "after" });
|
|
27
|
+
|
|
28
|
+
const page = item.printPage;
|
|
29
|
+
|
|
30
|
+
const src = cropUrl(manifest, page, item.bbox, { w: inline ? 640 : 1200 });
|
|
31
|
+
|
|
32
|
+
const srcset = cropSrcset(manifest, page, item.bbox, inline ? [320, 480, 640, 800] : undefined);
|
|
33
|
+
|
|
34
|
+
const sizes = inline ? "(min-width: 75rem) 352px, (min-width: 48rem) 288px, (min-width: 34rem) 46vw, calc(100vw - 2rem)" : "(min-width: 768px) 700px, 100vw";
|
|
35
|
+
|
|
36
|
+
const alt = item.advertiserName ? `Advertisement: ${item.advertiserName}` : "Advertisement";
|
|
37
|
+
|
|
38
|
+
const label = item.kind === "advertorial" ? "Sponsored" : "Advertisement";
|
|
39
|
+
|
|
40
|
+
const linked = Boolean(item.landingUrl);
|
|
41
|
+
|
|
42
|
+
// A trailing ad narrower than a page in print (a directory strip, a fractional
|
|
43
|
+
|
|
44
|
+
// unit) renders at roughly print scale instead of stretching to the column.
|
|
45
|
+
|
|
46
|
+
const scaled = !inline && item.bbox && item.bbox[2] < 0.9 ? `max-width: min(100%, ${Math.round(item.bbox[2] * 44)}rem);` : null;
|
|
47
|
+
|
|
48
|
+
out.w("<div" +
|
|
49
|
+
marko_classAttr(("edition-ad edition-ad--" + item.kind) + (inline ? " edition-ad--inline" : "")) +
|
|
50
|
+
marko_styleAttr(scaled) +
|
|
51
|
+
" data-unit-form=\"DIGITAL_EDITION\"" +
|
|
52
|
+
marko_attr("data-edition-ad-id", item.id) +
|
|
53
|
+
marko_attr("data-edition-issue-id", manifest.issueId) +
|
|
54
|
+
marko_attr("data-edition-page", page) +
|
|
55
|
+
marko_attr("data-edition-placement", inline ? "inline" : "after") +
|
|
56
|
+
marko_attr("data-advertiser", item.advertiserName || "") +
|
|
57
|
+
marko_attr("data-edition-entity", JSON.stringify(entity)) +
|
|
58
|
+
" data-p1-category=\"Digital Edition Ad Unit\"" +
|
|
59
|
+
marko_attr("data-p1-entity", entityAttr(entity)) +
|
|
60
|
+
"><span class=\"edition-ad__label\">" +
|
|
61
|
+
marko_escapeXml(label) +
|
|
62
|
+
"</span>");
|
|
63
|
+
|
|
64
|
+
if (linked) {
|
|
65
|
+
out.w("<a class=\"edition-ad__link\"" +
|
|
66
|
+
marko_attr("href", item.landingUrl) +
|
|
67
|
+
" target=\"_blank\" rel=\"nofollow noopener sponsored\" data-p1-action=\"Click\"" +
|
|
68
|
+
marko_attr("data-p1-label", item.advertiserName || "") +
|
|
69
|
+
"><img class=\"edition-ad__img\"" +
|
|
70
|
+
marko_attr("src", src) +
|
|
71
|
+
marko_attr("srcset", srcset) +
|
|
72
|
+
marko_attr("sizes", sizes) +
|
|
73
|
+
marko_attr("alt", alt) +
|
|
74
|
+
marko_attr("loading", eager ? "eager" : "lazy") +
|
|
75
|
+
" decoding=\"async\"></a>");
|
|
76
|
+
} else {
|
|
77
|
+
out.w("<img class=\"edition-ad__img\"" +
|
|
78
|
+
marko_attr("src", src) +
|
|
79
|
+
marko_attr("srcset", srcset) +
|
|
80
|
+
marko_attr("sizes", sizes) +
|
|
81
|
+
marko_attr("alt", alt) +
|
|
82
|
+
marko_attr("loading", eager ? "eager" : "lazy") +
|
|
83
|
+
" decoding=\"async\">");
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (item.advertiserName) {
|
|
87
|
+
out.w("<span class=\"edition-ad__advertiser\">" +
|
|
88
|
+
marko_escapeXml(item.advertiserName) +
|
|
89
|
+
"</span>");
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
out.w("</div>");
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
marko_template._ = marko_renderer(render, {
|
|
96
|
+
d_: true,
|
|
97
|
+
e_: marko_componentType
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
marko_template.meta = {
|
|
101
|
+
id: "/@mindful-web/marko-web-digital-edition$1.95.0/components/ad.marko"
|
|
102
|
+
};
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { anchorFor, teaserHtml, editionPath, articleAdLayout, cropUrl, cropSrcset } from "../lib";
|
|
2
|
+
import { articleEntity } from "../lib/entities";
|
|
3
|
+
|
|
4
|
+
$ const { manifest, item, canAccess, headingLevel } = input;
|
|
5
|
+
$ const entity = articleEntity(manifest, item);
|
|
6
|
+
$ const H = headingLevel === 1 ? "h1" : "h2";
|
|
7
|
+
$ const href = editionPath(manifest.issueId, { slug: item.slug });
|
|
8
|
+
$ const layout = canAccess ? articleAdLayout(manifest, item) : null;
|
|
9
|
+
|
|
10
|
+
<!--
|
|
11
|
+
One article. Shared by the hub (first article only), the standalone article page,
|
|
12
|
+
and the `?partial=1` fragment the scroll reader appends — so the three can never
|
|
13
|
+
drift apart.
|
|
14
|
+
-->
|
|
15
|
+
<article
|
|
16
|
+
class="edition-article"
|
|
17
|
+
id=anchorFor(item)
|
|
18
|
+
data-edition-article-slug=item.slug
|
|
19
|
+
data-edition-article-href=href
|
|
20
|
+
data-edition-print-page=item.printPage
|
|
21
|
+
data-edition-entity=JSON.stringify(entity)
|
|
22
|
+
data-edition-full-body=(canAccess ? "1" : null)
|
|
23
|
+
>
|
|
24
|
+
<header class="edition-article__header">
|
|
25
|
+
<p class="edition-article__page">
|
|
26
|
+
Page ${item.printPage}
|
|
27
|
+
<if(item.pageRefs && item.pageRefs.length > 1)>
|
|
28
|
+
<span class="edition-article__spread">· continues p. ${item.pageRefs.slice(1).join(", ")}</span>
|
|
29
|
+
</if>
|
|
30
|
+
</p>
|
|
31
|
+
|
|
32
|
+
<if(item.headline)>
|
|
33
|
+
<${H} class="edition-article__headline">${item.headline}</${H}>
|
|
34
|
+
</if>
|
|
35
|
+
<if(item.deck)>
|
|
36
|
+
<p class="edition-article__deck">${item.deck}</p>
|
|
37
|
+
</if>
|
|
38
|
+
|
|
39
|
+
<!--
|
|
40
|
+
Byline: the site's injected author renderer (e.g. the monorail author node with
|
|
41
|
+
a linked headshot) when the print byline resolved to a CMS contact, plain text
|
|
42
|
+
otherwise. See components/author.marko.
|
|
43
|
+
-->
|
|
44
|
+
<if(item.authorNode)>
|
|
45
|
+
<div class="edition-article__author">
|
|
46
|
+
<marko-web-digital-edition-author author=item.authorNode />
|
|
47
|
+
</div>
|
|
48
|
+
</if>
|
|
49
|
+
<else-if(item.byline)>
|
|
50
|
+
<p class="edition-article__byline">By ${item.byline}</p>
|
|
51
|
+
</else-if>
|
|
52
|
+
|
|
53
|
+
<if(item.match && item.match.path)>
|
|
54
|
+
<p class="edition-article__also">
|
|
55
|
+
Also published online:
|
|
56
|
+
<a href=item.match.path>${item.match.contentName || "read the web version"}</a>
|
|
57
|
+
</p>
|
|
58
|
+
</if>
|
|
59
|
+
</header>
|
|
60
|
+
|
|
61
|
+
<!--
|
|
62
|
+
Primary art, detected by the ingest from the PDF's image blocks (clustered
|
|
63
|
+
collage tiles, trimmed clear of any display type that would otherwise be
|
|
64
|
+
baked into the crop). Shown in both access states — it is the invitation.
|
|
65
|
+
-->
|
|
66
|
+
<if(item.leadImage)>
|
|
67
|
+
<figure class="edition-article__lead">
|
|
68
|
+
<img
|
|
69
|
+
src=cropUrl(manifest, item.leadImage.page, item.leadImage.bbox, { w: 1200 })
|
|
70
|
+
srcset=cropSrcset(manifest, item.leadImage.page, item.leadImage.bbox)
|
|
71
|
+
sizes="(min-width: 48rem) 672px, 100vw"
|
|
72
|
+
alt=(item.headline || "Article image")
|
|
73
|
+
width=Math.round(item.leadImage.bbox[2] * manifest.pdf.baseWidth)
|
|
74
|
+
height=Math.round(item.leadImage.bbox[3] * manifest.pdf.baseHeight)
|
|
75
|
+
loading=(headingLevel === 1 ? "eager" : "lazy")
|
|
76
|
+
decoding="async"
|
|
77
|
+
/>
|
|
78
|
+
</figure>
|
|
79
|
+
</if>
|
|
80
|
+
|
|
81
|
+
<!--
|
|
82
|
+
Full body: the print ads that sat inside this article's page run float into the
|
|
83
|
+
copy at matching depth (see edition/index.js inlineAdPlacements) — the ad div
|
|
84
|
+
must precede the paragraph it floats against INSIDE the body div, so it shares
|
|
85
|
+
the paragraphs' formatting context and the text wraps it.
|
|
86
|
+
-->
|
|
87
|
+
<if(canAccess)>
|
|
88
|
+
<div class="edition-article__body">
|
|
89
|
+
<for|seg| of=layout.segments>
|
|
90
|
+
<if(seg.ad)>
|
|
91
|
+
<marko-web-digital-edition-ad manifest=manifest item=seg.ad inline=true />
|
|
92
|
+
</if>
|
|
93
|
+
$!{seg.html}
|
|
94
|
+
</for>
|
|
95
|
+
</div>
|
|
96
|
+
</if>
|
|
97
|
+
<else>
|
|
98
|
+
<!--
|
|
99
|
+
Teaser plus one quiet, identical affordance per story. The fade says "there is
|
|
100
|
+
more"; this says how. The gate's script turns any click on the band or the
|
|
101
|
+
link into: open the sign-in bar (hub) or scroll to the inline panel (article
|
|
102
|
+
page) and focus the email field. Without JS the link is the article's own URL.
|
|
103
|
+
-->
|
|
104
|
+
<div class="edition-article__gated" data-edition-continue data-edition-slug=item.slug>
|
|
105
|
+
<div class="edition-article__body edition-article__body--teaser edition-gated-body">
|
|
106
|
+
$!{teaserHtml(item)}
|
|
107
|
+
</div>
|
|
108
|
+
<a class="edition-article__continue" href=href>
|
|
109
|
+
Continue reading<span class="edition-article__continue-arrow" aria-hidden="true">→</span>
|
|
110
|
+
</a>
|
|
111
|
+
</div>
|
|
112
|
+
</else>
|
|
113
|
+
|
|
114
|
+
<!-- Photo credits, pulled out of the body copy by the ingest cleanup. -->
|
|
115
|
+
<if(item.credits && item.credits.length)>
|
|
116
|
+
<p class="edition-article__credits">
|
|
117
|
+
<for|credit, i| of=item.credits>
|
|
118
|
+
<if(i)> · </if>${credit}
|
|
119
|
+
</for>
|
|
120
|
+
</p>
|
|
121
|
+
</if>
|
|
122
|
+
|
|
123
|
+
<!--
|
|
124
|
+
Claimed ads that cannot float (directory strips, overflow beyond the pacing
|
|
125
|
+
cap) trail the article at ~print scale. They render INSIDE <article> so the
|
|
126
|
+
`?partial=1` fragment stays single-child — the scroll reader appends fragment
|
|
127
|
+
children one by one, and sibling ads used to be dropped by that loop.
|
|
128
|
+
-->
|
|
129
|
+
<if(canAccess && layout.trailing.length)>
|
|
130
|
+
<for|ad| of=layout.trailing>
|
|
131
|
+
<marko-web-digital-edition-ad manifest=manifest item=ad />
|
|
132
|
+
</for>
|
|
133
|
+
</if>
|
|
134
|
+
</article>
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
// Compiled using marko@4.20.2 - DO NOT EDIT
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
var marko_template = module.exports = require("marko/dist/html").t(__filename),
|
|
5
|
+
marko_componentType = "/@mindful-web/marko-web-digital-edition$1.95.0/components/article-body.marko",
|
|
6
|
+
marko_renderer = require("marko/dist/runtime/components/renderer"),
|
|
7
|
+
module_lib_module = require("../lib"),
|
|
8
|
+
lib_module = module_lib_module.default || module_lib_module,
|
|
9
|
+
anchorFor = module_lib_module.anchorFor,
|
|
10
|
+
teaserHtml = module_lib_module.teaserHtml,
|
|
11
|
+
editionPath = module_lib_module.editionPath,
|
|
12
|
+
articleAdLayout = module_lib_module.articleAdLayout,
|
|
13
|
+
cropUrl = module_lib_module.cropUrl,
|
|
14
|
+
cropSrcset = module_lib_module.cropSrcset,
|
|
15
|
+
module_entities_module = require("../lib/entities"),
|
|
16
|
+
entities_module = module_entities_module.default || module_entities_module,
|
|
17
|
+
articleEntity = module_entities_module.articleEntity,
|
|
18
|
+
helpers_escape_xml = require("marko/dist/runtime/html/helpers/escape-xml"),
|
|
19
|
+
marko_escapeXml = helpers_escape_xml.x,
|
|
20
|
+
marko_dynamicTag = require("marko/dist/runtime/helpers/dynamic-tag"),
|
|
21
|
+
marko_web_digital_edition_author_template = require("./author.marko"),
|
|
22
|
+
marko_loadTag = require("marko/dist/runtime/helpers/load-tag"),
|
|
23
|
+
marko_web_digital_edition_author_tag = marko_loadTag(marko_web_digital_edition_author_template),
|
|
24
|
+
marko_attr = require("marko/dist/runtime/html/helpers/attr"),
|
|
25
|
+
marko_forOf = require("marko/dist/runtime/helpers/for-of"),
|
|
26
|
+
marko_web_digital_edition_ad_template = require("./ad.marko"),
|
|
27
|
+
marko_web_digital_edition_ad_tag = marko_loadTag(marko_web_digital_edition_ad_template),
|
|
28
|
+
marko_str = require("marko/dist/runtime/helpers/to-string");
|
|
29
|
+
|
|
30
|
+
function render(input, out, __component, component, state) {
|
|
31
|
+
var data = input;
|
|
32
|
+
|
|
33
|
+
const { manifest, item, canAccess, headingLevel } = input;
|
|
34
|
+
|
|
35
|
+
const entity = articleEntity(manifest, item);
|
|
36
|
+
|
|
37
|
+
const H = headingLevel === 1 ? "h1" : "h2";
|
|
38
|
+
|
|
39
|
+
const href = editionPath(manifest.issueId, { slug: item.slug });
|
|
40
|
+
|
|
41
|
+
const layout = canAccess ? articleAdLayout(manifest, item) : null;
|
|
42
|
+
|
|
43
|
+
out.w("<article class=\"edition-article\"" +
|
|
44
|
+
marko_attr("id", anchorFor(item)) +
|
|
45
|
+
marko_attr("data-edition-article-slug", item.slug) +
|
|
46
|
+
marko_attr("data-edition-article-href", href) +
|
|
47
|
+
marko_attr("data-edition-print-page", item.printPage) +
|
|
48
|
+
marko_attr("data-edition-entity", JSON.stringify(entity)) +
|
|
49
|
+
marko_attr("data-edition-full-body", canAccess ? "1" : null) +
|
|
50
|
+
"><header class=\"edition-article__header\"><p class=\"edition-article__page\">Page " +
|
|
51
|
+
marko_escapeXml(item.printPage));
|
|
52
|
+
|
|
53
|
+
if (item.pageRefs && (item.pageRefs.length > 1)) {
|
|
54
|
+
out.w("<span class=\"edition-article__spread\">· continues p. " +
|
|
55
|
+
marko_escapeXml(item.pageRefs.slice(1).join(", ")) +
|
|
56
|
+
"</span>");
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
out.w("</p>");
|
|
60
|
+
|
|
61
|
+
if (item.headline) {
|
|
62
|
+
marko_dynamicTag(out, H, function() {
|
|
63
|
+
return {
|
|
64
|
+
class: "edition-article__headline"
|
|
65
|
+
};
|
|
66
|
+
}, function(out) {
|
|
67
|
+
out.w(marko_escapeXml(item.headline));
|
|
68
|
+
}, null, null, __component, "4");
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (item.deck) {
|
|
72
|
+
out.w("<p class=\"edition-article__deck\">" +
|
|
73
|
+
marko_escapeXml(item.deck) +
|
|
74
|
+
"</p>");
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (item.authorNode) {
|
|
78
|
+
out.w("<div class=\"edition-article__author\">");
|
|
79
|
+
|
|
80
|
+
marko_web_digital_edition_author_tag({
|
|
81
|
+
author: item.authorNode
|
|
82
|
+
}, out, __component, "7");
|
|
83
|
+
|
|
84
|
+
out.w("</div>");
|
|
85
|
+
} else if (item.byline) {
|
|
86
|
+
out.w("<p class=\"edition-article__byline\">By " +
|
|
87
|
+
marko_escapeXml(item.byline) +
|
|
88
|
+
"</p>");
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (item.match && item.match.path) {
|
|
92
|
+
out.w("<p class=\"edition-article__also\">Also published online: <a" +
|
|
93
|
+
marko_attr("href", item.match.path) +
|
|
94
|
+
">" +
|
|
95
|
+
marko_escapeXml(item.match.contentName || "read the web version") +
|
|
96
|
+
"</a></p>");
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
out.w("</header>");
|
|
100
|
+
|
|
101
|
+
if (item.leadImage) {
|
|
102
|
+
out.w("<figure class=\"edition-article__lead\"><img" +
|
|
103
|
+
marko_attr("src", cropUrl(manifest, item.leadImage.page, item.leadImage.bbox, {
|
|
104
|
+
w: 1200
|
|
105
|
+
})) +
|
|
106
|
+
marko_attr("srcset", cropSrcset(manifest, item.leadImage.page, item.leadImage.bbox)) +
|
|
107
|
+
" sizes=\"(min-width: 48rem) 672px, 100vw\"" +
|
|
108
|
+
marko_attr("alt", item.headline || "Article image") +
|
|
109
|
+
marko_attr("width", Math.round(item.leadImage.bbox[2] * manifest.pdf.baseWidth)) +
|
|
110
|
+
marko_attr("height", Math.round(item.leadImage.bbox[3] * manifest.pdf.baseHeight)) +
|
|
111
|
+
marko_attr("loading", headingLevel === 1 ? "eager" : "lazy") +
|
|
112
|
+
" decoding=\"async\"></figure>");
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (canAccess) {
|
|
116
|
+
out.w("<div class=\"edition-article__body\">");
|
|
117
|
+
|
|
118
|
+
var $for$0 = 0;
|
|
119
|
+
|
|
120
|
+
marko_forOf(layout.segments, function(seg) {
|
|
121
|
+
var $keyScope$0 = "[" + (($for$0++) + "]");
|
|
122
|
+
|
|
123
|
+
if (seg.ad) {
|
|
124
|
+
marko_web_digital_edition_ad_tag({
|
|
125
|
+
manifest: manifest,
|
|
126
|
+
item: seg.ad,
|
|
127
|
+
inline: true
|
|
128
|
+
}, out, __component, "14" + $keyScope$0);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
out.w(marko_str(seg.html));
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
out.w("</div>");
|
|
135
|
+
} else {
|
|
136
|
+
out.w("<div class=\"edition-article__gated\" data-edition-continue" +
|
|
137
|
+
marko_attr("data-edition-slug", item.slug) +
|
|
138
|
+
"><div class=\"edition-article__body edition-article__body--teaser edition-gated-body\">" +
|
|
139
|
+
marko_str(teaserHtml(item)) +
|
|
140
|
+
"</div><a class=\"edition-article__continue\"" +
|
|
141
|
+
marko_attr("href", href) +
|
|
142
|
+
">Continue reading<span class=\"edition-article__continue-arrow\" aria-hidden=\"true\">→</span></a></div>");
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (item.credits && item.credits.length) {
|
|
146
|
+
out.w("<p class=\"edition-article__credits\">");
|
|
147
|
+
|
|
148
|
+
marko_forOf(item.credits, function(credit, i) {
|
|
149
|
+
if (i) {
|
|
150
|
+
out.w(" · ");
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
out.w(marko_escapeXml(credit));
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
out.w("</p>");
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (canAccess && layout.trailing.length) {
|
|
160
|
+
var $for$1 = 0;
|
|
161
|
+
|
|
162
|
+
marko_forOf(layout.trailing, function(ad) {
|
|
163
|
+
var $keyScope$1 = "[" + (($for$1++) + "]");
|
|
164
|
+
|
|
165
|
+
marko_web_digital_edition_ad_tag({
|
|
166
|
+
manifest: manifest,
|
|
167
|
+
item: ad
|
|
168
|
+
}, out, __component, "20" + $keyScope$1);
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
out.w("</article>");
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
marko_template._ = marko_renderer(render, {
|
|
176
|
+
d_: true,
|
|
177
|
+
e_: marko_componentType
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
marko_template.meta = {
|
|
181
|
+
id: "/@mindful-web/marko-web-digital-edition$1.95.0/components/article-body.marko",
|
|
182
|
+
tags: [
|
|
183
|
+
"./author.marko",
|
|
184
|
+
"./ad.marko"
|
|
185
|
+
]
|
|
186
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
$ const { author } = input;
|
|
2
|
+
$ const { authorNode } = (out.global.digitalEdition || {});
|
|
3
|
+
$ const path = author && author.siteContext && author.siteContext.path;
|
|
4
|
+
|
|
5
|
+
<!--
|
|
6
|
+
Byline for an article whose print byline resolved to a CMS contact.
|
|
7
|
+
|
|
8
|
+
The consuming site injects its own author renderer through the routes factory
|
|
9
|
+
(`authorNode`), e.g. the monorail theme's `nodes/author.marko`, so this package has no
|
|
10
|
+
dependency on any theme. Without one, a plain (optionally linked) name renders.
|
|
11
|
+
-->
|
|
12
|
+
<if(author)>
|
|
13
|
+
<if(authorNode)>
|
|
14
|
+
<${authorNode} author=author />
|
|
15
|
+
</if>
|
|
16
|
+
<else>
|
|
17
|
+
<p class="edition-article__byline">
|
|
18
|
+
By
|
|
19
|
+
<if(path)>
|
|
20
|
+
<a href=path>${author.name}</a>
|
|
21
|
+
</if>
|
|
22
|
+
<else>
|
|
23
|
+
${author.name}
|
|
24
|
+
</else>
|
|
25
|
+
<if(author.title)>
|
|
26
|
+
<span class="edition-article__byline-title">, ${author.title}</span>
|
|
27
|
+
</if>
|
|
28
|
+
</p>
|
|
29
|
+
</else>
|
|
30
|
+
</if>
|