openvibers/openvibe.deals Commits
Share: one Share sheet on every deal (#229)
Changes
@@@ -174,6 +174,9 @@ code { overflow-wrap: anywhere; }own buttons. This site carries no kit button styles, so the link wears the site's own. */.remind { display: inline-flex; align-items: center; gap: 5px; font-weight: 600; text-decoration: none; white-space: nowrap; }.remind-action { margin: 10px 0 0 10px; }++.deal-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; }++.deal-actions .button, .deal-actions button { margin-top: 0; }++.deal-actions .remind-action { margin: 0; }.remind .ovk-i { width: 1.05em; height: 1.05em; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }.hm-where { margin: 0; font-size: .82rem; overflow-wrap: anywhere; }.hm-chips { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: 8px; }@@@ -306,6 +306,7 @@ function createPages(ctx) {page: {agent: true,title: r.title,++ styles: ['kit.css'],description: `${views.priceText(v.latest)}${v.latest ? ` as of ${views.when(v.latest.observed_at)}` : ''}${r.seller ? ` at ${r.seller}` : v.store ? ` at ${v.store.name || v.store.domain}` : ''}.${r.description ? ` ${r.description.slice(0, 120)}` : ''}`,decision: v.decision,canonical,@@@ -8,6 +8,7 @@* an unknown one says "not stated". Nothing here calls a price "current".*/const { html, raw, renderMarkdown, paginationHtml, breadcrumbsHtml } = require('openvibe-publishing/ssr');++const kit = require('openvibe-shared/kit');const { AVAILABILITY_LABEL, CONDITIONS, AVAILABILITY } = require('../domain/values');const { savingOf } = require('../domain/price-trend');const { remindLink } = require('./remind');@@@ -265,6 +266,8 @@ function offerPage({ v, dto, viewer, csrf, myVote, comments: c, mergedComments,// "Remind me" for the moment the deal ends (plan L48 R3), beside the page's other actions; only when the end is// stated and still ahead.const remind = remindLink(r, { now: v.now, shop: v.store ? (v.store.name || v.store.domain) : '', url: urls.abs ? urls.abs(urls.offerPath(r)) : '', action: true });++ const shareUrl = urls.abs ? urls.abs(urls.offerPath(r)) : urls.offerPath(r);++ const shareText = r.ai_summary || null;const voteForm = signedIn && r.status !== 'disabled' ? (own ? html`<p class="meta">You posted this deal.</p>` : html`<form class="inline vote" method="post" action="${urls.offerPath(r)}/vote">${csrfField(csrf)}<button name="value" value="up"${myVote === 1 ? raw(' aria-pressed="true"') : ''}>▲ Hot</button><button name="value" value="down"${myVote === -1 ? raw(' aria-pressed="true"') : ''}>▼ Cold</button>@@@ -278,7 +281,7 @@ ${r.status === 'expired' ? html`<p class="notice">This deal expired${r.expired_a<section class="latest" aria-label="Latest observation">${latestLine(v)}${v.freshness.state === 'stale' ? html`<p class="notice stale-notice">This price was last observed ${timeEl(v.latest.observed_at)}. Deals shows it as history, not as the price now. If you see the deal, report what the page says below.</p>` : ''}--<p><a class="button" href="${safeHref(r.url)}" rel="nofollow noopener ugc">Go to the deal at ${v.store ? v.store.name || v.store.domain : 'the store'}</a>${remind ? raw(remind) : ''}</p>++<p class="deal-actions"><a class="button" href="${safeHref(r.url)}" rel="nofollow noopener ugc">Go to the deal at ${v.store ? v.store.name || v.store.domain : 'the store'}</a>${raw(kit.share({ title: r.title, url: shareUrl, text: shareText || '' }))}${remind ? raw(remind) : ''}</p>${r.expires_at && r.status === 'active' ? html`<p class="meta">Stated end: ${timeEl(r.expires_at)}</p>` : html`<p class="meta">End date: not stated</p>`}</section><section class="votes" aria-label="Votes">nnew file mode 100644@@@ -0,0 +1,25 @@++'use strict';++/**++ * Every deal carries the network's one Share button (openvibe-shared 4.114.0): one [data-ov-share-sheet] beside++ * "Go to the deal" and "Remind me", with the deal's canonical address, its title and its summary. The navbar loads++ * the sheet on the first press.++ */++const assert = require('assert');++const { boot } = require('./helpers/boot');++++(async () => {++ const t = await boot();++ try {++ const alice = t.network.addUser('alice');++ const offer = await t.submit(alice, { url: 'https://shop.example/headphones', title: 'Studio headphones', price: '49.99', currency: 'USD' });++ const r = await t.get(`/d/${offer.slug}`);++ assert.strictEqual(r.status, 200, r.text.slice(0, 200));++ assert.strictEqual((r.text.match(/data-ov-share-sheet/g) || []).length, 1, 'one Share button');++ assert.ok(r.text.includes(`data-url="https://openvibe.deals/d/${offer.slug}"`), 'the deal address');++ assert.ok(r.text.includes('data-title="Studio headphones"'), 'its title');++ assert.ok(r.text.includes('class="ovk-btn ovk-btn--quiet"') && r.text.includes('<span>Share</span>'), 'the shared kit button');++ console.log('deals share sheet ok');++ } finally {++ await t.close();++ }++})().catch((e) => { console.error(e); process.exit(1); });
@@ -174,6 +174,9 @@ code { overflow-wrap: anywhere; }
@@ -174,6 +174,9 @@ code { overflow-wrap: anywhere; }
own buttons. This site carries no kit button styles, so the link wears the site's own. */
own buttons. This site carries no kit button styles, so the link wears the site's own. */
.remind { display: inline-flex; align-items: center; gap: 5px; font-weight: 600; text-decoration: none; white-space: nowrap; }
.remind { display: inline-flex; align-items: center; gap: 5px; font-weight: 600; text-decoration: none; white-space: nowrap; }
.remind-action { margin: 10px 0 0 10px; }
.remind-action { margin: 10px 0 0 10px; }
+.deal-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; }
+.deal-actions .button, .deal-actions button { margin-top: 0; }
+.deal-actions .remind-action { margin: 0; }
.remind .ovk-i { width: 1.05em; height: 1.05em; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.remind .ovk-i { width: 1.05em; height: 1.05em; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.hm-where { margin: 0; font-size: .82rem; overflow-wrap: anywhere; }
.hm-where { margin: 0; font-size: .82rem; overflow-wrap: anywhere; }
.hm-chips { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: 8px; }
.hm-chips { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: 8px; }
@@ -306,6 +306,7 @@ function createPages(ctx) {
@@ -306,6 +306,7 @@ function createPages(ctx) {
page: {
page: {
agent: true,
agent: true,
title: r.title,
title: r.title,
+ styles: ['kit.css'],
description: `${views.priceText(v.latest)}${v.latest ? ` as of ${views.when(v.latest.observed_at)}` : ''}${r.seller ? ` at ${r.seller}` : v.store ? ` at ${v.store.name || v.store.domain}` : ''}.${r.description ? ` ${r.description.slice(0, 120)}` : ''}`,
description: `${views.priceText(v.latest)}${v.latest ? ` as of ${views.when(v.latest.observed_at)}` : ''}${r.seller ? ` at ${r.seller}` : v.store ? ` at ${v.store.name || v.store.domain}` : ''}.${r.description ? ` ${r.description.slice(0, 120)}` : ''}`,
decision: v.decision,
decision: v.decision,
canonical,
canonical,
@@ -8,6 +8,7 @@
@@ -8,6 +8,7 @@
* an unknown one says "not stated". Nothing here calls a price "current".
* an unknown one says "not stated". Nothing here calls a price "current".
*/
*/
const { html, raw, renderMarkdown, paginationHtml, breadcrumbsHtml } = require('openvibe-publishing/ssr');
const { html, raw, renderMarkdown, paginationHtml, breadcrumbsHtml } = require('openvibe-publishing/ssr');
+const kit = require('openvibe-shared/kit');
const { AVAILABILITY_LABEL, CONDITIONS, AVAILABILITY } = require('../domain/values');
const { AVAILABILITY_LABEL, CONDITIONS, AVAILABILITY } = require('../domain/values');
const { savingOf } = require('../domain/price-trend');
const { savingOf } = require('../domain/price-trend');
const { remindLink } = require('./remind');
const { remindLink } = require('./remind');
@@ -265,6 +266,8 @@ function offerPage({ v, dto, viewer, csrf, myVote, comments: c, mergedComments,
@@ -265,6 +266,8 @@ function offerPage({ v, dto, viewer, csrf, myVote, comments: c, mergedComments,
// "Remind me" for the moment the deal ends (plan L48 R3), beside the page's other actions; only when the end is
// "Remind me" for the moment the deal ends (plan L48 R3), beside the page's other actions; only when the end is
// stated and still ahead.
// stated and still ahead.
const remind = remindLink(r, { now: v.now, shop: v.store ? (v.store.name || v.store.domain) : '', url: urls.abs ? urls.abs(urls.offerPath(r)) : '', action: true });
const remind = remindLink(r, { now: v.now, shop: v.store ? (v.store.name || v.store.domain) : '', url: urls.abs ? urls.abs(urls.offerPath(r)) : '', action: true });
+ const shareUrl = urls.abs ? urls.abs(urls.offerPath(r)) : urls.offerPath(r);
+ const shareText = r.ai_summary || null;
const voteForm = signedIn && r.status !== 'disabled' ? (own ? html`<p class="meta">You posted this deal.</p>` : html`<form class="inline vote" method="post" action="${urls.offerPath(r)}/vote">${csrfField(csrf)}
const voteForm = signedIn && r.status !== 'disabled' ? (own ? html`<p class="meta">You posted this deal.</p>` : html`<form class="inline vote" method="post" action="${urls.offerPath(r)}/vote">${csrfField(csrf)}
<button name="value" value="up"${myVote === 1 ? raw(' aria-pressed="true"') : ''}>▲ Hot</button>
<button name="value" value="up"${myVote === 1 ? raw(' aria-pressed="true"') : ''}>▲ Hot</button>
<button name="value" value="down"${myVote === -1 ? raw(' aria-pressed="true"') : ''}>▼ Cold</button>
<button name="value" value="down"${myVote === -1 ? raw(' aria-pressed="true"') : ''}>▼ Cold</button>
@@ -278,7 +281,7 @@ ${r.status === 'expired' ? html`<p class="notice">This deal expired${r.expired_a
@@ -278,7 +281,7 @@ ${r.status === 'expired' ? html`<p class="notice">This deal expired${r.expired_a
<section class="latest" aria-label="Latest observation">
<section class="latest" aria-label="Latest observation">
${latestLine(v)}
${latestLine(v)}
${v.freshness.state === 'stale' ? html`<p class="notice stale-notice">This price was last observed ${timeEl(v.latest.observed_at)}. Deals shows it as history, not as the price now. If you see the deal, report what the page says below.</p>` : ''}
${v.freshness.state === 'stale' ? html`<p class="notice stale-notice">This price was last observed ${timeEl(v.latest.observed_at)}. Deals shows it as history, not as the price now. If you see the deal, report what the page says below.</p>` : ''}
-<p><a class="button" href="${safeHref(r.url)}" rel="nofollow noopener ugc">Go to the deal at ${v.store ? v.store.name || v.store.domain : 'the store'}</a>${remind ? raw(remind) : ''}</p>
+<p class="deal-actions"><a class="button" href="${safeHref(r.url)}" rel="nofollow noopener ugc">Go to the deal at ${v.store ? v.store.name || v.store.domain : 'the store'}</a>${raw(kit.share({ title: r.title, url: shareUrl, text: shareText || '' }))}${remind ? raw(remind) : ''}</p>
${r.expires_at && r.status === 'active' ? html`<p class="meta">Stated end: ${timeEl(r.expires_at)}</p>` : html`<p class="meta">End date: not stated</p>`}
${r.expires_at && r.status === 'active' ? html`<p class="meta">Stated end: ${timeEl(r.expires_at)}</p>` : html`<p class="meta">End date: not stated</p>`}
</section>
</section>
<section class="votes" aria-label="Votes">
<section class="votes" aria-label="Votes">
new file mode 100644
new file mode 100644
@@ -0,0 +1,25 @@
@@ -0,0 +1,25 @@
+'use strict';
+/**
+ * Every deal carries the network's one Share button (openvibe-shared 4.114.0): one [data-ov-share-sheet] beside
+ * "Go to the deal" and "Remind me", with the deal's canonical address, its title and its summary. The navbar loads
+ * the sheet on the first press.
+ */
+const assert = require('assert');
+const { boot } = require('./helpers/boot');
+
+(async () => {
+ const t = await boot();
+ try {
+ const alice = t.network.addUser('alice');
+ const offer = await t.submit(alice, { url: 'https://shop.example/headphones', title: 'Studio headphones', price: '49.99', currency: 'USD' });
+ const r = await t.get(`/d/${offer.slug}`);
+ assert.strictEqual(r.status, 200, r.text.slice(0, 200));
+ assert.strictEqual((r.text.match(/data-ov-share-sheet/g) || []).length, 1, 'one Share button');
+ assert.ok(r.text.includes(`data-url="https://openvibe.deals/d/${offer.slug}"`), 'the deal address');
+ assert.ok(r.text.includes('data-title="Studio headphones"'), 'its title');
+ assert.ok(r.text.includes('class="ovk-btn ovk-btn--quiet"') && r.text.includes('<span>Share</span>'), 'the shared kit button');
+ console.log('deals share sheet ok');
+ } finally {
+ await t.close();
+ }
+})().catch((e) => { console.error(e); process.exit(1); });