Skip to content

Commit e32ea02

Browse files
authored
Merge pull request #9447 from quarto-dev/feature/listing-img-lazy
listings - add 'lazy: false' to item to control lazy loading of images
2 parents 84704e6 + d0ff78c commit e32ea02

28 files changed

+317
-18
lines changed

news/changelog-1.5.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ All changes included in 1.5:
8080
- ([#8715](https://github.com/quarto-dev/quarto-cli/issues/8715)): Listings should respect `image: false`
8181
- ([#8860](https://github.com/quarto-dev/quarto-cli/discussions/8860)): Don't show duplicate author names.
8282
- ([#9030](https://github.com/quarto-dev/quarto-cli/discussions/9030)): Warn (rather than error) when listing globs produce an empty listing (as this is permissable).
83+
- ([#9447](https://github.com/quarto-dev/quarto-cli/pull/9447)): Add support for the boolean `image-lazy-loading` option to enable lazy loading of images in listings (default: `true`).
8384

8485
## Manuscripts
8586

src/project/types/website/listing/website-listing-read.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ import {
6262
kImageAlign,
6363
kImageAlt,
6464
kImageHeight,
65+
kImageLazyLoading,
6566
kImagePlaceholder,
6667
kInclude,
6768
kListing,
@@ -117,6 +118,7 @@ import {
117118
isProjectDraft,
118119
projectDraftMode,
119120
} from "../website-utils.ts";
121+
import { kFieldImageLazyLoading } from "./website-listing-shared.ts";
120122

121123
// Defaults (a card listing that contains everything
122124
// in the source document's directory)
@@ -1117,6 +1119,9 @@ async function listItemFromFile(
11171119
: undefined;
11181120

11191121
const imageAlt = documentMeta?.[kImageAlt] as string | undefined;
1122+
const imageLazyLoading = documentMeta?.[kImageLazyLoading] as
1123+
| boolean
1124+
| undefined;
11201125

11211126
const date = documentMeta?.date
11221127
? parsePandocDate(resolveDate(input, documentMeta?.date) as string)
@@ -1170,6 +1175,7 @@ async function listItemFromFile(
11701175
[kFieldCategories]: categories,
11711176
[kFieldImage]: image,
11721177
[kFieldImageAlt]: imageAlt,
1178+
[kFieldImageLazyLoading]: imageLazyLoading,
11731179
[kFieldDescription]: description,
11741180
[kFieldFileName]: filename,
11751181
[kFieldFileModified]: filemodified,

src/project/types/website/listing/website-listing-shared.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,9 @@ export const kImageAlign = "image-align";
7373
// Alt text for the item's image
7474
export const kImageAlt = "image-alt";
7575

76+
// Lazy loading for the item's image. If unset, the default is true.
77+
export const kImageLazyLoading = "image-lazy-loading";
78+
7679
// The placeholder image for the item
7780
export const kImagePlaceholder = "image-placeholder";
7881

@@ -101,6 +104,7 @@ export const kFieldFileModified = "file-modified";
101104
export const kFieldDate = "date";
102105
export const kFieldImage = "image";
103106
export const kFieldImageAlt = "image-alt";
107+
export const kFieldImageLazyLoading = "image-lazy-loading";
104108
export const kFieldDescription = "description";
105109
export const kFieldReadingTime = "reading-time";
106110
export const kFieldWordCount = "word-count";
@@ -211,6 +215,7 @@ export interface ListingItem extends Record<string, unknown> {
211215
date?: Date;
212216
image?: string;
213217
[kImageAlt]?: string;
218+
[kImageLazyLoading]?: boolean;
214219
path?: string;
215220
filename?: string;
216221
[kFieldFileModified]?: Date;

src/project/types/website/listing/website-listing-template.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ export function templateMarkdownHandler(
9595
// For file modified specifically, include the time portion
9696
const includeTime = field === kFieldFileModified;
9797

98-
const date = typeof (dateRaw) === "string"
98+
const date = typeof dateRaw === "string"
9999
? parsePandocDate(dateRaw as string)
100100
: dateRaw as Date;
101101
if (date) {
@@ -422,6 +422,7 @@ export function reshapeListing(
422422
src: string,
423423
classes: string,
424424
alt?: string,
425+
lazy?: boolean,
425426
) => {
426427
const pageSize = listing[kPageSize];
427428
const classAttr = classes ? `class="${classes}"` : "";
@@ -430,8 +431,8 @@ export function reshapeListing(
430431
: "";
431432
const altAttr = alt ? `alt="${encodeAttributeValue(alt)}"` : "";
432433
const srcAttr = itemNumber > pageSize ? "data-src" : "src";
433-
434-
return `<img ${srcAttr}="${src}" ${classAttr} ${styleAttr} ${altAttr}>`;
434+
const lazyAttr = lazy === false ? "" : "loading='lazy' ";
435+
return `<img ${lazyAttr}${srcAttr}="${src}" ${classAttr} ${styleAttr} ${altAttr}>`;
435436
};
436437
utilities.imgPlaceholder = (
437438
itemNumber: number,

src/resources/editor/tools/vs-code.mjs

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10597,6 +10597,12 @@ var require_yaml_intelligence_resources = __commonJS({
1059710597
description: "The default image to use if an item in the listing doesn't have an image."
1059810598
}
1059910599
},
10600+
"image-lazy-loading": {
10601+
boolean: {
10602+
description: "If false, images in the listing will be loaded immediately. If true, images will be loaded as they come into view.",
10603+
default: true
10604+
}
10605+
},
1060010606
"image-align": {
1060110607
enum: [
1060210608
"left",
@@ -18472,6 +18478,20 @@ var require_yaml_intelligence_resources = __commonJS({
1847218478
]
1847318479
},
1847418480
description: "The alt text for preview image on this page."
18481+
},
18482+
{
18483+
name: "image-lazy-loading",
18484+
schema: "boolean",
18485+
tags: {
18486+
formats: [
18487+
"$html-doc"
18488+
]
18489+
},
18490+
description: {
18491+
short: "If true, the preview image will only load when it comes into view.",
18492+
long: 'Enables lazy loading for the preview image. If true, the preview image element \nwill have `loading="lazy"`, and will only load when it comes into view.\n\nIf false, the preview image will load immediately.\n'
18493+
},
18494+
default: true
1847518495
}
1847618496
],
1847718497
"schema/extension.yml": [
@@ -21151,6 +21171,7 @@ var require_yaml_intelligence_resources = __commonJS({
2115121171
"The name to display in the UI.",
2115221172
"The name of the language the kernel implements.",
2115321173
"The name of the kernel.",
21174+
"Configures the Julia engine.",
2115421175
"Arguments to pass to the Julia worker process.",
2115521176
"Environment variables to pass to the Julia worker process.",
2115621177
"Set Knitr options.",
@@ -22744,7 +22765,11 @@ var require_yaml_intelligence_resources = __commonJS({
2274422765
},
2274522766
"Disambiguating year suffix in author-date styles (e.g.&nbsp;\u201Ca\u201D in \u201CDoe,\n1999a\u201D).",
2274622767
"Manuscript configuration",
22747-
"internal-schema-hack"
22768+
"internal-schema-hack",
22769+
{
22770+
short: "If true, the preview image will only load when it comes into\nview.",
22771+
long: 'Enables lazy loading for the preview image. If true, the preview\nimage element will have <code>loading="lazy"</code>, and will only load\nwhen it comes into view.\nIf false, the preview image will load immediately.'
22772+
}
2274822773
],
2274922774
"schema/external-schemas.yml": [
2275022775
{
@@ -22973,12 +22998,12 @@ var require_yaml_intelligence_resources = __commonJS({
2297322998
mermaid: "%%"
2297422999
},
2297523000
"handlers/mermaid/schema.yml": {
22976-
_internalId: 183687,
23001+
_internalId: 186197,
2297723002
type: "object",
2297823003
description: "be an object",
2297923004
properties: {
2298023005
"mermaid-format": {
22981-
_internalId: 183679,
23006+
_internalId: 186189,
2298223007
type: "enum",
2298323008
enum: [
2298423009
"png",
@@ -22994,7 +23019,7 @@ var require_yaml_intelligence_resources = __commonJS({
2299423019
exhaustiveCompletions: true
2299523020
},
2299623021
theme: {
22997-
_internalId: 183686,
23022+
_internalId: 186196,
2299823023
type: "anyOf",
2299923024
anyOf: [
2300023025
{

src/resources/editor/tools/yaml/web-worker.js

Lines changed: 29 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/resources/editor/tools/yaml/yaml-intelligence-resources.json

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3569,6 +3569,12 @@
35693569
"description": "The default image to use if an item in the listing doesn't have an image."
35703570
}
35713571
},
3572+
"image-lazy-loading": {
3573+
"boolean": {
3574+
"description": "If false, images in the listing will be loaded immediately. If true, images will be loaded as they come into view.",
3575+
"default": true
3576+
}
3577+
},
35723578
"image-align": {
35733579
"enum": [
35743580
"left",
@@ -11444,6 +11450,20 @@
1144411450
]
1144511451
},
1144611452
"description": "The alt text for preview image on this page."
11453+
},
11454+
{
11455+
"name": "image-lazy-loading",
11456+
"schema": "boolean",
11457+
"tags": {
11458+
"formats": [
11459+
"$html-doc"
11460+
]
11461+
},
11462+
"description": {
11463+
"short": "If true, the preview image will only load when it comes into view.",
11464+
"long": "Enables lazy loading for the preview image. If true, the preview image element \nwill have `loading=\"lazy\"`, and will only load when it comes into view.\n\nIf false, the preview image will load immediately.\n"
11465+
},
11466+
"default": true
1144711467
}
1144811468
],
1144911469
"schema/extension.yml": [
@@ -14123,6 +14143,7 @@
1412314143
"The name to display in the UI.",
1412414144
"The name of the language the kernel implements.",
1412514145
"The name of the kernel.",
14146+
"Configures the Julia engine.",
1412614147
"Arguments to pass to the Julia worker process.",
1412714148
"Environment variables to pass to the Julia worker process.",
1412814149
"Set Knitr options.",
@@ -15716,7 +15737,11 @@
1571615737
},
1571715738
"Disambiguating year suffix in author-date styles (e.g.&nbsp;“a” in “Doe,\n1999a”).",
1571815739
"Manuscript configuration",
15719-
"internal-schema-hack"
15740+
"internal-schema-hack",
15741+
{
15742+
"short": "If true, the preview image will only load when it comes into\nview.",
15743+
"long": "Enables lazy loading for the preview image. If true, the preview\nimage element will have <code>loading=\"lazy\"</code>, and will only load\nwhen it comes into view.\nIf false, the preview image will load immediately."
15744+
}
1572015745
],
1572115746
"schema/external-schemas.yml": [
1572215747
{
@@ -15945,12 +15970,12 @@
1594515970
"mermaid": "%%"
1594615971
},
1594715972
"handlers/mermaid/schema.yml": {
15948-
"_internalId": 183687,
15973+
"_internalId": 186197,
1594915974
"type": "object",
1595015975
"description": "be an object",
1595115976
"properties": {
1595215977
"mermaid-format": {
15953-
"_internalId": 183679,
15978+
"_internalId": 186189,
1595415979
"type": "enum",
1595515980
"enum": [
1595615981
"png",
@@ -15966,7 +15991,7 @@
1596615991
"exhaustiveCompletions": true
1596715992
},
1596815993
"theme": {
15969-
"_internalId": 183686,
15994+
"_internalId": 186196,
1597015995
"type": "anyOf",
1597115996
"anyOf": [
1597215997
{

src/resources/projects/website/listing/item-default.ejs.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ print(`<div class="metadata-value listing-${field}">${listing.utilities.outputLi
4141
<div class="thumbnail">
4242
<a href="<%- item.path %>" class="no-external">
4343
<% if (item.image) { %>
44-
<%= listing.utilities.img(itemNumber, item.image, "thumbnail-image", item['image-alt']) %>
44+
<%= listing.utilities.img(itemNumber, item.image, "thumbnail-image", item['image-alt'], item['image-lazy-loading'] ?? listing['image-lazy-loading']) %>
4545
<% } else { %>
4646
<%= listing.utilities.imgPlaceholder(itemNumber, item.outputHref) %>
4747
<% } %>

src/resources/projects/website/listing/item-grid.ejs.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ return !["title", "image", "image-alt", "date", "author", "subtitle", "descripti
4646
<% if (item.image) { %>
4747

4848
<p class="card-img-top">
49-
<%= listing.utilities.img(itemNumber, item.image, "thumbnail-image card-img", item['image-alt']) %>
49+
<%= listing.utilities.img(itemNumber, item.image, "thumbnail-image card-img", item['image-alt'], item['image-lazy-loading'] ?? listing['image-lazy-loading']) %>
5050
</p>
5151
<% } else { %>
5252
<%= listing.utilities.imgPlaceholder(itemNumber, item.outputHref) %>

src/resources/projects/website/listing/listing-table.ejs.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ let value = readField(item, field);
5555

5656
if (field === "image") {
5757
if (item.image) {
58-
value = listing.utilities.img(itemNumber, item[field], "", item['image-alt']);
58+
value = listing.utilities.img(itemNumber, item[field], "", item['image-alt'], item['image-lazy-loading'] ?? listing['image-lazy-loading']);
5959
} else {
6060
value = listing.utilities.imgPlaceholder(itemNumber, item.outputHref);
6161
}

0 commit comments

Comments
 (0)