pennyrouter 0.3.45 → 0.3.46
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/package.json
CHANGED
|
@@ -315,6 +315,7 @@
|
|
|
315
315
|
<div class="segmented" role="radiogroup" aria-label="Featured page">
|
|
316
316
|
<label><input type="radio" name="publish-cover" value="penny" checked><span>Penny page</span></label>
|
|
317
317
|
<label><input type="radio" name="publish-cover" value="native"><span>Native page</span></label>
|
|
318
|
+
<label><input type="radio" name="publish-cover" value="none"><span>No page</span></label>
|
|
318
319
|
</div>
|
|
319
320
|
<label>Your name, as shown publicly
|
|
320
321
|
<input id="publish-name" maxlength="40" required autocomplete="nickname"></label>
|
|
@@ -962,13 +963,9 @@
|
|
|
962
963
|
}
|
|
963
964
|
}
|
|
964
965
|
const sideOf = (role) => (paneRoles.left === role ? 'left' : 'right');
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
return 'Publishing needs an HTML page in each pane.';
|
|
969
|
-
}
|
|
970
|
-
return '';
|
|
971
|
-
};
|
|
966
|
+
// Pages are optional: a comparison can be shared with just its numbers and a screenshot.
|
|
967
|
+
const publishBlocker = () =>
|
|
968
|
+
gallery ? '' : 'Publishing needs a native pane and a Penny pane on the same harness.';
|
|
972
969
|
const prepareGallery = (message) => {
|
|
973
970
|
lastReport = message;
|
|
974
971
|
gallery = message.gallery || null;
|
|
@@ -993,7 +990,22 @@
|
|
|
993
990
|
const pagePolicy = "default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline'; img-src data: blob:; " +
|
|
994
991
|
"font-src data:; connect-src 'none'; frame-src 'none'; object-src 'none'; base-uri 'none'; form-action 'none'";
|
|
995
992
|
const sandboxed = (html) => `<meta http-equiv="Content-Security-Policy" content="${pagePolicy}"><meta name="referrer" content="no-referrer">${html}`;
|
|
996
|
-
|
|
993
|
+
// The page chosen for a side, or undefined when "No page" is selected.
|
|
994
|
+
const chosen = (role) => {
|
|
995
|
+
const value = $(role === 'NATIVE' ? 'publish-native' : 'publish-penny').value;
|
|
996
|
+
return value === '' ? undefined : snapshot[sideOf(role)][Number(value)];
|
|
997
|
+
};
|
|
998
|
+
// The featured page must be one that is shared; fall back to the other page, then to none.
|
|
999
|
+
const syncFeatured = () => {
|
|
1000
|
+
const available = { penny: Boolean(chosen('PENNY')), native: Boolean(chosen('NATIVE')) };
|
|
1001
|
+
for (const radio of document.querySelectorAll('input[name="publish-cover"]')) {
|
|
1002
|
+
radio.disabled = radio.value !== 'none' && !available[radio.value];
|
|
1003
|
+
}
|
|
1004
|
+
if (document.querySelector('input[name="publish-cover"]:checked').disabled) {
|
|
1005
|
+
const next = available.penny ? 'penny' : available.native ? 'native' : 'none';
|
|
1006
|
+
document.querySelector(`input[name="publish-cover"][value="${next}"]`).checked = true;
|
|
1007
|
+
}
|
|
1008
|
+
};
|
|
997
1009
|
const checkedValue = (name) => document.querySelector(`input[name="${name}"]:checked`).value;
|
|
998
1010
|
const savingsText = (savings) => {
|
|
999
1011
|
if (!savings) return '';
|
|
@@ -1037,8 +1049,11 @@
|
|
|
1037
1049
|
$('preview-savings').classList.toggle('loss', Boolean(savings && savings.cost < 0));
|
|
1038
1050
|
const model = (meterSnapshots[sideOf('PENNY')]?.model || '').split('/').pop();
|
|
1039
1051
|
$('preview-meta').textContent = [model, $('publish-name').value.trim()].filter(Boolean).join(' · ');
|
|
1040
|
-
|
|
1041
|
-
const
|
|
1052
|
+
syncFeatured();
|
|
1053
|
+
const featured = checkedValue('publish-cover');
|
|
1054
|
+
const cover = featured === 'none' ? undefined : chosen(featured === 'native' ? 'NATIVE' : 'PENNY');
|
|
1055
|
+
$('publish-preview').hidden = !cover;
|
|
1056
|
+
const key = `${featured}:${cover?.path}`;
|
|
1042
1057
|
if (cover && key !== previewedCover) {
|
|
1043
1058
|
previewedCover = key;
|
|
1044
1059
|
$('publish-preview').srcdoc = sandboxed(cover.content);
|
|
@@ -1048,7 +1063,9 @@
|
|
|
1048
1063
|
for (const [role, id] of [['NATIVE', 'publish-native'], ['PENNY', 'publish-penny']]) {
|
|
1049
1064
|
const select = $(id);
|
|
1050
1065
|
if (!select.options.length) {
|
|
1066
|
+
select.add(new Option('No page', ''));
|
|
1051
1067
|
snapshot[sideOf(role)].forEach((file, index) => select.add(new Option(file.path, String(index))));
|
|
1068
|
+
select.value = snapshot[sideOf(role)].length ? '0' : '';
|
|
1052
1069
|
}
|
|
1053
1070
|
}
|
|
1054
1071
|
if (!$('publish-name').value) $('publish-name').value = remembered('penny-compare-display-name');
|
|
@@ -1083,6 +1100,7 @@
|
|
|
1083
1100
|
if (published) return;
|
|
1084
1101
|
const native = chosen('NATIVE');
|
|
1085
1102
|
const penny = chosen('PENNY');
|
|
1103
|
+
const featured = checkedValue('publish-cover');
|
|
1086
1104
|
const video = $('publish-video').files[0] || null;
|
|
1087
1105
|
const videoType = video && (video.type || (/\.webm$/i.test(video.name) ? 'video/webm' : /\.mp4$/i.test(video.name) ? 'video/mp4' : ''));
|
|
1088
1106
|
$('publish-error').textContent = '';
|
|
@@ -1096,7 +1114,8 @@
|
|
|
1096
1114
|
}
|
|
1097
1115
|
const meta = { title: $('publish-title').value.trim(), comment: $('publish-comment').value.trim(),
|
|
1098
1116
|
tag: checkedValue('publish-tag'), display_name: $('publish-name').value.trim(),
|
|
1099
|
-
native_file: native
|
|
1117
|
+
native_file: native?.path ?? null, penny_file: penny?.path ?? null,
|
|
1118
|
+
cover: featured === 'none' ? null : featured,
|
|
1100
1119
|
video_type: video ? videoType : null };
|
|
1101
1120
|
const submit = $('publish-submit');
|
|
1102
1121
|
submit.disabled = true;
|
|
@@ -1106,13 +1125,13 @@
|
|
|
1106
1125
|
try {
|
|
1107
1126
|
if (hosted) {
|
|
1108
1127
|
result = await relayToParent({ report: gallery.report, signature: gallery.signature,
|
|
1109
|
-
reportNonce: gallery.reportNonce, meta, native: native
|
|
1128
|
+
reportNonce: gallery.reportNonce, meta, native: native?.content ?? null, penny: penny?.content ?? null, video,
|
|
1110
1129
|
cover: coverImage });
|
|
1111
1130
|
} else {
|
|
1112
1131
|
const form = new FormData();
|
|
1113
1132
|
form.set('meta', JSON.stringify(meta));
|
|
1114
|
-
form.set('native', new Blob([native.content], { type: 'text/html' }), 'native.html');
|
|
1115
|
-
form.set('penny', new Blob([penny.content], { type: 'text/html' }), 'penny.html');
|
|
1133
|
+
if (native) form.set('native', new Blob([native.content], { type: 'text/html' }), 'native.html');
|
|
1134
|
+
if (penny) form.set('penny', new Blob([penny.content], { type: 'text/html' }), 'penny.html');
|
|
1116
1135
|
if (video) form.set('video', video, video.name);
|
|
1117
1136
|
if (coverImage) form.set('cover', coverImage, 'cover');
|
|
1118
1137
|
const response = await fetch('/gallery/submit', { method: 'POST',
|
|
@@ -1173,8 +1192,8 @@
|
|
|
1173
1192
|
window.addEventListener('message', (event) => {
|
|
1174
1193
|
if (event.origin !== config.parentOrigin || event.source !== window.parent) return;
|
|
1175
1194
|
if (event.data?.type === 'compare-demo-start' && authenticated) {
|
|
1176
|
-
const { harness, nativeKey, pennyKey, environment: chosen } = event.data;
|
|
1177
|
-
send({ type: 'start', harness, nativeKey, pennyKey, environment: chosen });
|
|
1195
|
+
const { harness, model, nativeKey, pennyKey, environment: chosen } = event.data;
|
|
1196
|
+
send({ type: 'start', harness, model, nativeKey, pennyKey, environment: chosen });
|
|
1178
1197
|
setStatus('Starting terminals…');
|
|
1179
1198
|
return;
|
|
1180
1199
|
}
|
|
@@ -25,7 +25,7 @@ export async function savedPennyKey() {
|
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
/** Sends the report, the chosen pages, and an optional video; returns the new entry's id. A
|
|
28
|
+
/** Sends the report, the chosen pages (each optional), and an optional video; returns the new entry's id. A
|
|
29
29
|
* report signed by its demo sandbox (the subscription launcher) carries that signature. */
|
|
30
30
|
export async function publishToGallery(report, form, { signature = '', reportNonce = '' } = {}) {
|
|
31
31
|
const key = await savedPennyKey();
|
|
@@ -37,8 +37,7 @@ export async function publishToGallery(report, form, { signature = '', reportNon
|
|
|
37
37
|
outgoing.set('meta', String(form.get('meta') ?? ''));
|
|
38
38
|
for (const side of ['native', 'penny']) {
|
|
39
39
|
const page = form.get(side);
|
|
40
|
-
if (
|
|
41
|
-
outgoing.set(side, page, `${side}.html`);
|
|
40
|
+
if (page instanceof Blob) outgoing.set(side, page, `${side}.html`);
|
|
42
41
|
}
|
|
43
42
|
const cover = form.get('cover');
|
|
44
43
|
if (cover instanceof Blob && cover.size) outgoing.set('cover', cover, 'cover');
|