Skip to content

Commit c7ab3b5

Browse files
committed
Merge branch 'main' of github.com:quarto-dev/quarto-cli into main
2 parents 9785a4f + 18cad5f commit c7ab3b5

File tree

4 files changed

+148
-21
lines changed

4 files changed

+148
-21
lines changed

src/core/text.ts

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,3 +47,98 @@ export function formatLineRange(
4747
lines: result,
4848
};
4949
}
50+
51+
const kLastPunctuationRegex = /([\S\s]*)[\.\?\!]/;
52+
function trimSentence(text: string) {
53+
const match = text.match(kLastPunctuationRegex);
54+
if (match) {
55+
return {
56+
text: match[0],
57+
trimmed: true,
58+
};
59+
} else {
60+
return {
61+
text,
62+
trimmed: false,
63+
};
64+
}
65+
}
66+
67+
function trimLength(text: string, length: number) {
68+
if (text.length < length) {
69+
return {
70+
text,
71+
trimmed: false,
72+
};
73+
} else {
74+
return {
75+
text: text.substring(0, length),
76+
trimmed: true,
77+
};
78+
}
79+
}
80+
81+
function trimSpace(text: string) {
82+
const lastSpace = text.lastIndexOf(" ");
83+
if (lastSpace > 0) {
84+
return {
85+
text: text.substring(0, lastSpace),
86+
trimmed: true,
87+
};
88+
} else {
89+
return {
90+
text,
91+
trimmed: false,
92+
};
93+
}
94+
}
95+
96+
export function truncateText(
97+
text: string,
98+
length: number,
99+
breakAt: "space" | "punctuation",
100+
) {
101+
const trimEnd = (text: string) => {
102+
if ([",", "/", ":"].includes(text.charAt(text.length - 1))) {
103+
return text.substring(0, text.length - 1);
104+
} else {
105+
return text;
106+
}
107+
};
108+
109+
const trimAtSpace = (text: string) => {
110+
console.log(text);
111+
const spaceResult = trimSpace(
112+
text.substring(0, text.length - 1),
113+
);
114+
console.log(spaceResult.text);
115+
return trimEnd(spaceResult.text) + "…";
116+
};
117+
118+
const trimPunc = (text: string) => {
119+
const puncResult = trimSentence(text);
120+
if (puncResult.trimmed) {
121+
return puncResult.text;
122+
} else {
123+
return trimAtSpace(puncResult.text);
124+
}
125+
};
126+
127+
const lengthResult = trimLength(text, length);
128+
129+
if (lengthResult.trimmed) {
130+
// This was shortened
131+
if (breakAt === "punctuation") {
132+
return trimPunc(lengthResult.text);
133+
} else {
134+
return trimAtSpace(lengthResult.text);
135+
}
136+
} else {
137+
// This wasn't shortened
138+
if (breakAt === "punctuation") {
139+
return trimPunc(lengthResult.text);
140+
} else {
141+
return trimEnd(lengthResult.text);
142+
}
143+
}
144+
}

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

Lines changed: 8 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ import {
4141
import { resourcePath } from "../../../../core/resources.ts";
4242
import { localizedString } from "../../../../config/localization.ts";
4343
import { formatDate, parsePandocDate } from "../../../../core/date.ts";
44+
import { truncateText } from "../../../../core/text.ts";
4445

4546
export const kDateFormat = "date-format";
4647

@@ -111,7 +112,11 @@ export function templateMarkdownHandler(
111112
const maxDescLength = listing[kMaxDescLength] as number ||
112113
-1;
113114
if (maxDescLength > 0) {
114-
record.description = truncateText(item.description, maxDescLength);
115+
record.description = truncateText(
116+
item.description,
117+
maxDescLength,
118+
"space",
119+
);
115120
}
116121
}
117122

@@ -198,14 +203,14 @@ export function templateMarkdownHandler(
198203
if (content) {
199204
content.appendChild(listingEl);
200205
} else {
201-
// Custom page layout doesn't have a main.content, so
206+
// Custom page layout doesn't have a main.content, so
202207
// just use the quarto-content div directly
203208
const customContent = doc.querySelector("#quarto-content");
204209
if (customContent) {
205210
customContent.appendChild(listingEl);
206211
} else {
207212
// Couldn't find anywhere to put the listing el, just
208-
// stick at the bottom of the body
213+
// stick at the bottom of the body
209214
doc.body.appendChild(listingEl);
210215
}
211216
}
@@ -513,19 +518,3 @@ export function templateJsScript(
513518
`;
514519
return jsScript;
515520
}
516-
517-
function truncateText(text: string, length: number) {
518-
if (text.length < length) {
519-
return text;
520-
} else {
521-
// Since we'll insert elips, trim an extra space
522-
const clipLength = length - 1;
523-
const clipped = text.substring(0, clipLength);
524-
const lastSpace = clipped.lastIndexOf(" ");
525-
if (lastSpace > 0) {
526-
return clipped.substring(0, lastSpace) + "…";
527-
} else {
528-
return clipped + "…";
529-
}
530-
}
531-
}

src/project/types/website/util/discover-meta.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,19 @@ const kMdPreviewClassRegex = RegExp(kPreviewClassPattern);
2727
const kMdNamedImageRegex = RegExp(kMdNamedImagePattern);
2828
const kMarkdownImg = /!\[[^\]]*\]\((.*?)(?:\".*\")?\)(?:\{(?:[^\|]*)\})?/;
2929

30+
export function findDescription(doc: Document): string | undefined {
31+
const paras = doc.querySelectorAll(
32+
"main.content > p, main.content > section > p",
33+
);
34+
for (const para of paras) {
35+
const paraEl = para as Element;
36+
if (paraEl.innerText) {
37+
return paraEl.innerText;
38+
}
39+
}
40+
return undefined;
41+
}
42+
3043
export function findPreviewImg(
3144
doc: Document,
3245
): string | undefined {

src/project/types/website/website-meta.ts

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ import {
3636
createMarkdownPipeline,
3737
MarkdownPipeline,
3838
} from "./website-pipeline-md.ts";
39-
import { findPreviewImg } from "./util/discover-meta.ts";
39+
import { findDescription, findPreviewImg } from "./util/discover-meta.ts";
4040
import { isAbsoluteRef } from "../../../core/http.ts";
4141
import {
4242
kHtmlEmptyPostProcessResult,
@@ -45,6 +45,7 @@ import { HtmlPostProcessResult } from "../../../command/render/types.ts";
4545
import { imageSize } from "../../../core/image.ts";
4646
import { writeMetaTag } from "../../../format/html/format-html-shared.ts";
4747
import { joinUrl } from "../../../core/url.ts";
48+
import { truncateText } from "../../../core/text.ts";
4849

4950
const kCard = "card";
5051

@@ -53,6 +54,7 @@ interface SocialMetadataProvider {
5354
prefix: string;
5455
metadata: Metadata;
5556
filter?: (key: string) => string;
57+
resolveValue?: (key: string, value: string) => string;
5658
resolveDefaults?: (finalMetadata: Metadata) => void;
5759
}
5860

@@ -101,6 +103,14 @@ export function metadataHtmlPostProcessor(
101103
}
102104
return key;
103105
},
106+
resolveValue: (key: string, value: string) => {
107+
// Limit to 300 chars for Open Graph
108+
if ([kDescription].includes(key)) {
109+
return truncateText(value, 200, "punctuation");
110+
}
111+
112+
return value;
113+
},
104114
};
105115

106116
// The twitter card provider
@@ -122,6 +132,14 @@ export function metadataHtmlPostProcessor(
122132

123133
return key;
124134
},
135+
resolveValue: (key: string, value: string) => {
136+
// Limit to 200 chars for Twitter
137+
if ([kDescription].includes(key)) {
138+
return truncateText(value, 200, "punctuation");
139+
}
140+
141+
return value;
142+
},
125143
resolveDefaults: (finalMetadata: Metadata) => {
126144
if (finalMetadata[kCardStyle] === undefined) {
127145
finalMetadata[kCardStyle] = finalMetadata[kImage]
@@ -156,6 +174,11 @@ export function metadataHtmlPostProcessor(
156174
metadata[kImage] = findPreviewImg(doc);
157175
}
158176

177+
// cook up a description if one is not provided
178+
if (metadata[kDescription] === undefined) {
179+
metadata[kDescription] = findDescription(doc);
180+
}
181+
159182
// Convert image to absolute href and add height and width
160183
resolveImageMetadata(source, project, format, metadata);
161184

@@ -167,11 +190,18 @@ export function metadataHtmlPostProcessor(
167190
// Append the metadata
168191
Object.keys(metadata).forEach((key) => {
169192
if (metadata[key] !== undefined) {
193+
// Resolve the value
170194
const data = metadata[key] as string;
195+
const value = provider.resolveValue
196+
? provider.resolveValue(key, data)
197+
: data;
198+
199+
// Filter the key
171200
if (provider.filter) {
172201
key = provider.filter(key);
173202
}
174-
writeMetaTag(`${provider.prefix}:${key}`, data, doc);
203+
204+
writeMetaTag(`${provider.prefix}:${key}`, value, doc);
175205
}
176206
});
177207
});

0 commit comments

Comments
 (0)