@rathnasgala2/template 3.1.0 → 3.2.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/README.md +136 -43
- package/package.json +2 -2
- package/src/core/index.js +48 -8
- package/src/core/internal/appearance/base-layer.js +1 -0
- package/src/core/internal/chrome.js +5 -2
- package/src/core/internal/components.js +69 -10
- package/src/core/internal/editions.js +297 -0
- package/src/core/internal/feeds.js +8 -18
- package/src/core/internal/media/content-images.js +262 -0
- package/src/core/internal/media/gif-probe.js +162 -0
- package/src/core/internal/media/jpeg-codec.js +0 -0
- package/src/core/internal/media/limits.js +21 -0
- package/src/core/internal/media/pipeline.js +73 -40
- package/src/core/internal/media/sniff.js +15 -2
- package/src/core/internal/messages.js +10 -1
- package/src/core/internal/page-kinds.js +97 -25
- package/src/core/internal/skeleton.js +25 -4
- package/src/core/internal/source-inventory.js +4 -0
- package/src/modules/interactions/browser/gala-interactions.js +298 -13
- package/types/index.d.ts +31 -1
|
@@ -30,6 +30,22 @@
|
|
|
30
30
|
* POSTs carry an `Idempotency-Key` that is reused while the same draft is
|
|
31
31
|
* retried.
|
|
32
32
|
*
|
|
33
|
+
* Appeals: a signed-in reader's thread read also carries that reader's own
|
|
34
|
+
* removed or held comments, each with an `own` object (`state` REMOVED or HELD,
|
|
35
|
+
* the `appeal` so far or null, and `canAppeal`) and no body. Such a comment is
|
|
36
|
+
* shown as "Removed by the site's moderators" (held: "Hidden while the
|
|
37
|
+
* moderators review it"), headed by a one-line context, "Your comment from
|
|
38
|
+
* <relative date>" (from `createdAt`), that tells the reader which comment it
|
|
39
|
+
* is; when `canAppeal` is true it offers an Appeal button that opens a note
|
|
40
|
+
* field (up to 1,000 characters) and posts `{note}` to
|
|
41
|
+
* `/v2/public/comments/{commentId}/appeals` with the bearer token. The
|
|
42
|
+
* appeal's state (pending, restored, kept removed) is written under the
|
|
43
|
+
* comment, and only there: sending an appeal updates the comment's state and
|
|
44
|
+
* redraws it, it never adds a sentence of its own, so "Appeal sent. ..." shows
|
|
45
|
+
* once, from the state, whether the reader has just sent it or the thread was
|
|
46
|
+
* read again. There is one appeal per removal; the moderators decide, never the
|
|
47
|
+
* script.
|
|
48
|
+
*
|
|
33
49
|
* Not used, by design: iframes, popups (`window.open`), `postMessage`,
|
|
34
50
|
* FedCM, cookies, third-party scripts or requests.
|
|
35
51
|
*
|
|
@@ -116,6 +132,22 @@
|
|
|
116
132
|
showMoreReplies: 'Show more replies',
|
|
117
133
|
deletedTombstone: 'This comment was deleted.',
|
|
118
134
|
removedTombstone: 'This comment was removed.',
|
|
135
|
+
ownFrom: 'Your comment from ',
|
|
136
|
+
removedOwn: "Removed by the site's moderators",
|
|
137
|
+
heldOwn: 'Hidden while the moderators review it',
|
|
138
|
+
appeal: 'Appeal',
|
|
139
|
+
appealAria: 'Appeal this decision',
|
|
140
|
+
appealLabel:
|
|
141
|
+
'Tell the moderators why this should be restored (up to 1,000 characters)',
|
|
142
|
+
appealSend: 'Send',
|
|
143
|
+
appealSent: 'Appeal sent. The moderators will look again.',
|
|
144
|
+
appealRestored: 'Restored after review',
|
|
145
|
+
appealDenied: 'Kept removed after review',
|
|
146
|
+
appealEmpty: 'Write why you are appealing first.',
|
|
147
|
+
appealTooLong: "That's longer than 1,000 characters.",
|
|
148
|
+
appealExists: 'You already appealed this decision.',
|
|
149
|
+
appealNotAllowed: "This comment can't be appealed.",
|
|
150
|
+
appealTooMany: "You've sent several appeals today. Try again tomorrow.",
|
|
119
151
|
reportTitle: 'Report this comment',
|
|
120
152
|
reportReason: 'Reason',
|
|
121
153
|
reportNote: 'Details (optional)',
|
|
@@ -196,7 +228,9 @@
|
|
|
196
228
|
* @typedef {{accessToken: string, expiresAt: string, reader: ReaderView}} Session
|
|
197
229
|
* @typedef {{lowerBound: number, display: string, label: string}} PublicCount
|
|
198
230
|
* @typedef {{isAuthor: boolean, canEdit: boolean, canDelete: boolean, canReply: boolean}} CommentViewer
|
|
199
|
-
* @typedef {{
|
|
231
|
+
* @typedef {{state: 'PENDING' | 'RESTORED' | 'DENIED', decidedAt?: string}} CommentAppeal
|
|
232
|
+
* @typedef {{state: 'REMOVED' | 'HELD', appeal: CommentAppeal | null, canAppeal: boolean}} CommentOwn
|
|
233
|
+
* @typedef {{id: string, parentId: string | null, depth: number, state: 'VISIBLE' | 'REMOVED' | 'DELETED' | 'HELD', author: ReaderView | null, body: string | null, createdAt: string, editedAt: string | null, replies: CommentView[], moreReplies: boolean, viewer: CommentViewer, own?: CommentOwn}} CommentView
|
|
200
234
|
* @typedef {{items: CommentView[], nextCursor: string | null, count: PublicCount | null}} CommentPage
|
|
201
235
|
* @typedef {{key: string, label: string, visual: {kind: string, token: string}, count: PublicCount | null, viewerActive: boolean}} ReactionItem
|
|
202
236
|
* @typedef {{enabled: boolean, items: ReactionItem[]}} ReactionsBlock
|
|
@@ -1006,6 +1040,22 @@
|
|
|
1006
1040
|
}
|
|
1007
1041
|
}
|
|
1008
1042
|
|
|
1043
|
+
/**
|
|
1044
|
+
* A comment's `<time>` element: the given text, the machine-readable date
|
|
1045
|
+
* and the full date as its tooltip.
|
|
1046
|
+
*
|
|
1047
|
+
* @param {string} iso
|
|
1048
|
+
* @param {string} text
|
|
1049
|
+
* @returns {HTMLElement}
|
|
1050
|
+
*/
|
|
1051
|
+
function timeElement(iso, text) {
|
|
1052
|
+
const time = make('time', 'g-comment__time', text);
|
|
1053
|
+
time.setAttribute('datetime', iso);
|
|
1054
|
+
const title = fullDate(iso);
|
|
1055
|
+
if (title) time.setAttribute('title', title);
|
|
1056
|
+
return time;
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1009
1059
|
// ------------------------------------------------------------------ comments
|
|
1010
1060
|
/**
|
|
1011
1061
|
* @param {PublicCount|null|undefined} count
|
|
@@ -1023,13 +1073,65 @@
|
|
|
1023
1073
|
}
|
|
1024
1074
|
}
|
|
1025
1075
|
|
|
1076
|
+
/**
|
|
1077
|
+
* The state of the signed-in reader's own removed or held comment, which
|
|
1078
|
+
* arrives with an `own` object and no body.
|
|
1079
|
+
*
|
|
1080
|
+
* @param {CommentView} comment
|
|
1081
|
+
* @returns {CommentOwn | null}
|
|
1082
|
+
*/
|
|
1083
|
+
function ownView(comment) {
|
|
1084
|
+
const own = comment.own;
|
|
1085
|
+
return own &&
|
|
1086
|
+
typeof own === 'object' &&
|
|
1087
|
+
(own.state === 'REMOVED' || own.state === 'HELD')
|
|
1088
|
+
? own
|
|
1089
|
+
: null;
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
/**
|
|
1093
|
+
* The one-line context of the reader's own removed or held comment, which
|
|
1094
|
+
* arrives with no body: "Your comment from <relative date>". Null when the
|
|
1095
|
+
* comment has no usable `createdAt`.
|
|
1096
|
+
*
|
|
1097
|
+
* @param {CommentView} comment
|
|
1098
|
+
* @returns {HTMLElement | null}
|
|
1099
|
+
*/
|
|
1100
|
+
function ownContext(comment) {
|
|
1101
|
+
if (!Number.isFinite(Date.parse(comment.createdAt))) return null;
|
|
1102
|
+
return timeElement(
|
|
1103
|
+
comment.createdAt,
|
|
1104
|
+
MESSAGES.ownFrom + timeLabel(comment.createdAt, false),
|
|
1105
|
+
);
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
/**
|
|
1109
|
+
* The line saying where the reader's appeal stands, if there is one.
|
|
1110
|
+
*
|
|
1111
|
+
* @param {CommentView} comment
|
|
1112
|
+
* @returns {HTMLElement | null}
|
|
1113
|
+
*/
|
|
1114
|
+
function appealResult(comment) {
|
|
1115
|
+
const appeal = comment.own && comment.own.appeal;
|
|
1116
|
+
const text = !appeal
|
|
1117
|
+
? ''
|
|
1118
|
+
: appeal.state === 'PENDING'
|
|
1119
|
+
? MESSAGES.appealSent
|
|
1120
|
+
: appeal.state === 'RESTORED'
|
|
1121
|
+
? MESSAGES.appealRestored
|
|
1122
|
+
: appeal.state === 'DENIED'
|
|
1123
|
+
? MESSAGES.appealDenied
|
|
1124
|
+
: '';
|
|
1125
|
+
return text ? make('p', 'g-interactions__notice', text) : null;
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1026
1128
|
/**
|
|
1027
1129
|
* @param {CommentView} comment
|
|
1028
1130
|
* @returns {boolean}
|
|
1029
1131
|
*/
|
|
1030
1132
|
function canReplyTo(comment) {
|
|
1031
1133
|
if (!live.commentsOpen || !live.allowReplies) return false;
|
|
1032
|
-
if (comment.state !== 'VISIBLE') return false;
|
|
1134
|
+
if (comment.state !== 'VISIBLE' || ownView(comment)) return false;
|
|
1033
1135
|
if (comment.depth >= live.maxDepth - 1) return false;
|
|
1034
1136
|
if (session && comment.viewer && comment.viewer.canReply === false) {
|
|
1035
1137
|
return false;
|
|
@@ -1061,31 +1163,49 @@
|
|
|
1061
1163
|
const existingReplies = item.querySelector(':scope > .g-comment__replies');
|
|
1062
1164
|
while (item.firstChild) item.removeChild(item.firstChild);
|
|
1063
1165
|
item._comment = comment;
|
|
1064
|
-
|
|
1166
|
+
const own = ownView(comment);
|
|
1167
|
+
item.classList.toggle(
|
|
1168
|
+
'g-comment--tombstone',
|
|
1169
|
+
comment.state !== 'VISIBLE' || !!own,
|
|
1170
|
+
);
|
|
1065
1171
|
item.classList.toggle(
|
|
1066
1172
|
'g-comment--mine',
|
|
1067
|
-
!!(comment.viewer && comment.viewer.isAuthor),
|
|
1173
|
+
!!own || !!(comment.viewer && comment.viewer.isAuthor),
|
|
1068
1174
|
);
|
|
1069
1175
|
|
|
1070
1176
|
const head = make('div', 'g-comment__head');
|
|
1071
|
-
if (
|
|
1177
|
+
if (own) {
|
|
1178
|
+
// No body comes back for these, so say which comment it is by its date.
|
|
1179
|
+
const context = ownContext(comment);
|
|
1180
|
+
if (context) item.appendChild(context);
|
|
1181
|
+
item.appendChild(
|
|
1182
|
+
make(
|
|
1183
|
+
'p',
|
|
1184
|
+
'g-comment__body',
|
|
1185
|
+
own.state === 'HELD' ? MESSAGES.heldOwn : MESSAGES.removedOwn,
|
|
1186
|
+
),
|
|
1187
|
+
);
|
|
1188
|
+
const result = appealResult(comment);
|
|
1189
|
+
if (result) item.appendChild(result);
|
|
1190
|
+
if (own.canAppeal === true) item.appendChild(renderAppealAction(item));
|
|
1191
|
+
} else if (comment.state === 'VISIBLE' && comment.author) {
|
|
1072
1192
|
const avatar = make('span', 'g-comment__avatar', comment.author.initials);
|
|
1073
1193
|
avatar.setAttribute('aria-hidden', 'true');
|
|
1074
1194
|
head.appendChild(avatar);
|
|
1075
1195
|
head.appendChild(
|
|
1076
1196
|
make('span', 'g-comment__author', comment.author.displayName),
|
|
1077
1197
|
);
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1198
|
+
head.appendChild(
|
|
1199
|
+
timeElement(
|
|
1200
|
+
comment.createdAt,
|
|
1201
|
+
timeLabel(comment.createdAt, !!comment.editedAt),
|
|
1202
|
+
),
|
|
1082
1203
|
);
|
|
1083
|
-
time.setAttribute('datetime', comment.createdAt);
|
|
1084
|
-
const title = fullDate(comment.createdAt);
|
|
1085
|
-
if (title) time.setAttribute('title', title);
|
|
1086
|
-
head.appendChild(time);
|
|
1087
1204
|
item.appendChild(head);
|
|
1088
1205
|
item.appendChild(renderBody(comment.body || ''));
|
|
1206
|
+
// A comment restored on appeal says so.
|
|
1207
|
+
const result = appealResult(comment);
|
|
1208
|
+
if (result) item.appendChild(result);
|
|
1089
1209
|
item.appendChild(renderActions(item, comment));
|
|
1090
1210
|
} else {
|
|
1091
1211
|
item.appendChild(
|
|
@@ -1721,6 +1841,171 @@
|
|
|
1721
1841
|
focusNode(select);
|
|
1722
1842
|
}
|
|
1723
1843
|
|
|
1844
|
+
// -------------------------------------------------------------------- appeal
|
|
1845
|
+
/**
|
|
1846
|
+
* @param {CommentItem} item
|
|
1847
|
+
* @returns {HTMLElement}
|
|
1848
|
+
*/
|
|
1849
|
+
function renderAppealAction(item) {
|
|
1850
|
+
const actions = make('div', 'g-comment__actions');
|
|
1851
|
+
const appeal = button(
|
|
1852
|
+
'g-comment__action',
|
|
1853
|
+
MESSAGES.appeal,
|
|
1854
|
+
MESSAGES.appealAria,
|
|
1855
|
+
);
|
|
1856
|
+
appeal.addEventListener('click', () => openAppeal(item, appeal));
|
|
1857
|
+
actions.appendChild(appeal);
|
|
1858
|
+
return actions;
|
|
1859
|
+
}
|
|
1860
|
+
|
|
1861
|
+
/**
|
|
1862
|
+
* The sentence for a failed appeal; the problems an appeal adds are named
|
|
1863
|
+
* here, everything else is worded as for any other request.
|
|
1864
|
+
*
|
|
1865
|
+
* @param {unknown} error
|
|
1866
|
+
* @returns {string}
|
|
1867
|
+
*/
|
|
1868
|
+
function appealFailureMessage(error) {
|
|
1869
|
+
if (error instanceof ApiError && error.kind === 'problem') {
|
|
1870
|
+
if (error.code === 'APPEAL_EXISTS') return MESSAGES.appealExists;
|
|
1871
|
+
if (error.code === 'APPEAL_NOT_ALLOWED') return MESSAGES.appealNotAllowed;
|
|
1872
|
+
if (error.status === 429 || error.code === 'RATE_LIMITED') {
|
|
1873
|
+
return MESSAGES.appealTooMany;
|
|
1874
|
+
}
|
|
1875
|
+
}
|
|
1876
|
+
return failureMessage(error);
|
|
1877
|
+
}
|
|
1878
|
+
|
|
1879
|
+
/**
|
|
1880
|
+
* Show the new state of an appeal under its comment.
|
|
1881
|
+
*
|
|
1882
|
+
* @param {CommentItem} item
|
|
1883
|
+
* @param {CommentAppeal | null} appeal
|
|
1884
|
+
*/
|
|
1885
|
+
function applyAppeal(item, appeal) {
|
|
1886
|
+
const comment = item._comment;
|
|
1887
|
+
fillComment(
|
|
1888
|
+
item,
|
|
1889
|
+
Object.assign({}, comment, {
|
|
1890
|
+
own: Object.assign({}, comment.own, { appeal, canAppeal: false }),
|
|
1891
|
+
}),
|
|
1892
|
+
);
|
|
1893
|
+
focusNode(item);
|
|
1894
|
+
}
|
|
1895
|
+
|
|
1896
|
+
/**
|
|
1897
|
+
* @param {CommentItem} item
|
|
1898
|
+
* @param {HTMLElement} trigger
|
|
1899
|
+
*/
|
|
1900
|
+
function openAppeal(item, trigger) {
|
|
1901
|
+
if (!session) {
|
|
1902
|
+
startSignIn({ kind: 'signin' });
|
|
1903
|
+
return;
|
|
1904
|
+
}
|
|
1905
|
+
const wrapper = make('div', 'g-composer');
|
|
1906
|
+
const inputId = uniqueId('appeal');
|
|
1907
|
+
const label = make('label', '', MESSAGES.appealLabel);
|
|
1908
|
+
label.setAttribute('for', inputId);
|
|
1909
|
+
const input = make('textarea', 'g-composer__input');
|
|
1910
|
+
input.id = inputId;
|
|
1911
|
+
input.rows = 3;
|
|
1912
|
+
input.setAttribute('maxlength', String(NOTE_LIMIT));
|
|
1913
|
+
const error = make('p', 'g-composer__error');
|
|
1914
|
+
error.setAttribute('role', 'alert');
|
|
1915
|
+
error.hidden = true;
|
|
1916
|
+
const actions = make('div', 'g-composer__actions');
|
|
1917
|
+
const send = button('g-composer__submit', MESSAGES.appealSend);
|
|
1918
|
+
const cancel = button('g-composer__cancel', MESSAGES.cancel);
|
|
1919
|
+
actions.appendChild(send);
|
|
1920
|
+
actions.appendChild(cancel);
|
|
1921
|
+
for (const node of [label, input, error, actions]) {
|
|
1922
|
+
wrapper.appendChild(node);
|
|
1923
|
+
}
|
|
1924
|
+
|
|
1925
|
+
/**
|
|
1926
|
+
* @param {string} text
|
|
1927
|
+
*/
|
|
1928
|
+
const showError = (text) => {
|
|
1929
|
+
error.textContent = text;
|
|
1930
|
+
error.hidden = !text;
|
|
1931
|
+
if (text) announceNow(text);
|
|
1932
|
+
};
|
|
1933
|
+
const close = () => {
|
|
1934
|
+
wrapper.remove();
|
|
1935
|
+
trigger.hidden = false;
|
|
1936
|
+
};
|
|
1937
|
+
cancel.addEventListener('click', () => {
|
|
1938
|
+
close();
|
|
1939
|
+
focusNode(trigger);
|
|
1940
|
+
});
|
|
1941
|
+
input.addEventListener('input', () => showError(''));
|
|
1942
|
+
input.addEventListener('keydown', (event) => {
|
|
1943
|
+
if (event.key === 'Escape') {
|
|
1944
|
+
event.preventDefault();
|
|
1945
|
+
cancel.click();
|
|
1946
|
+
}
|
|
1947
|
+
});
|
|
1948
|
+
send.addEventListener('click', async () => {
|
|
1949
|
+
if (!session) {
|
|
1950
|
+
startSignIn({ kind: 'signin' });
|
|
1951
|
+
return;
|
|
1952
|
+
}
|
|
1953
|
+
const note = input.value.trim();
|
|
1954
|
+
if (!note) {
|
|
1955
|
+
showError(MESSAGES.appealEmpty);
|
|
1956
|
+
return;
|
|
1957
|
+
}
|
|
1958
|
+
if (codePoints(note) > NOTE_LIMIT) {
|
|
1959
|
+
showError(MESSAGES.appealTooLong);
|
|
1960
|
+
return;
|
|
1961
|
+
}
|
|
1962
|
+
send.disabled = true;
|
|
1963
|
+
send.setAttribute('aria-busy', 'true');
|
|
1964
|
+
showError('');
|
|
1965
|
+
try {
|
|
1966
|
+
await api(
|
|
1967
|
+
'POST',
|
|
1968
|
+
'/v2/public/comments/' +
|
|
1969
|
+
encodeURIComponent(item._comment.id) +
|
|
1970
|
+
'/appeals',
|
|
1971
|
+
{ body: { note } },
|
|
1972
|
+
);
|
|
1973
|
+
// "Appeal sent" is drawn by the redraw alone, under the comment where
|
|
1974
|
+
// focus lands; the status line does not repeat it.
|
|
1975
|
+
close();
|
|
1976
|
+
applyAppeal(item, { state: 'PENDING' });
|
|
1977
|
+
} catch (failure) {
|
|
1978
|
+
const message = appealFailureMessage(failure);
|
|
1979
|
+
if (
|
|
1980
|
+
failure instanceof ApiError &&
|
|
1981
|
+
(failure.code === 'APPEAL_EXISTS' ||
|
|
1982
|
+
failure.code === 'APPEAL_NOT_ALLOWED')
|
|
1983
|
+
) {
|
|
1984
|
+
// The server's answer is final for this removal: stop offering it.
|
|
1985
|
+
close();
|
|
1986
|
+
applyAppeal(
|
|
1987
|
+
item,
|
|
1988
|
+
failure.code === 'APPEAL_EXISTS'
|
|
1989
|
+
? item._comment.own?.appeal || { state: 'PENDING' }
|
|
1990
|
+
: item._comment.own?.appeal || null,
|
|
1991
|
+
);
|
|
1992
|
+
announce(message);
|
|
1993
|
+
} else {
|
|
1994
|
+
send.disabled = false;
|
|
1995
|
+
showError(message);
|
|
1996
|
+
}
|
|
1997
|
+
} finally {
|
|
1998
|
+
send.removeAttribute('aria-busy');
|
|
1999
|
+
}
|
|
2000
|
+
});
|
|
2001
|
+
|
|
2002
|
+
trigger.hidden = true;
|
|
2003
|
+
const replies = item.querySelector(':scope > .g-comment__replies');
|
|
2004
|
+
if (replies) item.insertBefore(wrapper, replies);
|
|
2005
|
+
else item.appendChild(wrapper);
|
|
2006
|
+
focusNode(input);
|
|
2007
|
+
}
|
|
2008
|
+
|
|
1724
2009
|
// ------------------------------------------------------------------- loading
|
|
1725
2010
|
/**
|
|
1726
2011
|
* @param {CommentPage} page
|
package/types/index.d.ts
CHANGED
|
@@ -54,10 +54,23 @@ export interface RenderableBody {
|
|
|
54
54
|
renderPolicy: { name: string; version: string; digest: string };
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
+
/**
|
|
58
|
+
* A `contentEdition`-shaped object: which article a `kind: "edition"` document
|
|
59
|
+
* restates, at what depth, and where it came from. A stale edition never
|
|
60
|
+
* reaches the renderer, so `sourceDigest` is carried but not compared.
|
|
61
|
+
*/
|
|
62
|
+
export interface ContentEdition {
|
|
63
|
+
of: string;
|
|
64
|
+
kind: 'QUICK_READ' | 'STANDARD' | 'DEEP_DIVE';
|
|
65
|
+
sourceDigest: string;
|
|
66
|
+
generation: { provider: string; model: string; generationId: string };
|
|
67
|
+
approvedAt: string;
|
|
68
|
+
}
|
|
69
|
+
|
|
57
70
|
/** A `contentFrontmatterNormalized`-shaped record's fields this renderer reads. */
|
|
58
71
|
export interface ContentFrontmatterNormalized {
|
|
59
72
|
id: string;
|
|
60
|
-
kind: 'article' | 'page';
|
|
73
|
+
kind: 'article' | 'page' | 'edition';
|
|
61
74
|
title: string;
|
|
62
75
|
description?: string;
|
|
63
76
|
language: string;
|
|
@@ -74,6 +87,20 @@ export interface ContentFrontmatterNormalized {
|
|
|
74
87
|
hero?: ResolvedMediaRef;
|
|
75
88
|
socialImage?: ResolvedFileRef;
|
|
76
89
|
redirects: string[];
|
|
90
|
+
/** Present exactly when `kind` is `edition`. */
|
|
91
|
+
edition?: ContentEdition;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* A `contentBuildMediaFile`-shaped entry: one repository file a document
|
|
96
|
+
* references. Every image the document's body references must be one of
|
|
97
|
+
* these (an image media type).
|
|
98
|
+
*/
|
|
99
|
+
export interface ContentBuildMediaFile {
|
|
100
|
+
path: string;
|
|
101
|
+
sourceDigest: string;
|
|
102
|
+
mediaType: string;
|
|
103
|
+
byteLength: number;
|
|
77
104
|
}
|
|
78
105
|
|
|
79
106
|
/** A `contentBuildRecord`-shaped record. */
|
|
@@ -87,6 +114,7 @@ export interface ContentBuildRecord {
|
|
|
87
114
|
sourceRevision: string;
|
|
88
115
|
sourceDigest: string;
|
|
89
116
|
resolvedAuthorIds: string[];
|
|
117
|
+
media?: ContentBuildMediaFile[];
|
|
90
118
|
}
|
|
91
119
|
|
|
92
120
|
/** A `socialLink`-shaped reference. */
|
|
@@ -174,6 +202,8 @@ export interface AppearanceNormalized {
|
|
|
174
202
|
fontAssets: ResolvedFileRef[];
|
|
175
203
|
tokens: Record<string, never>;
|
|
176
204
|
source: NormalizedSource;
|
|
205
|
+
/** The footer attribution switch; absent means the attribution shows. */
|
|
206
|
+
attribution?: { showMadeWith: boolean };
|
|
177
207
|
}
|
|
178
208
|
|
|
179
209
|
/** The subset of `build-input:2.0.0` this renderer reads. */
|