openvibers/openvibe.wiki Commits
Share: one Share sheet on every article (#219)
Changes
@@@ -2,6 +2,7 @@## Unreleased++- Share: every article carries the network's one Share button (`data-ov-share-sheet`) in its page actions, with the article's canonical address, title and summary; `openvibe-shared`'s navbar loads the sheet on the first press. `test/share-sheet.test.js`.- Account export and deletion (ADR-033): `POST /internal/events` (loopback, `WIKI_EVENTS_SECRET`) answers network.account.export_requested and network.account.deleted through `server/wiki/account-data.js` over `openvibe-sdk/account-data` (SDK v0.37.0, was v0.35.0).- **Deleted:** the person's own user spaces (through `deleteSpace`), roles, watches and drafts.- **Authorless:** their contributions elsewhere stay without their id.@@@ -415,8 +415,9 @@ function createPages({ svc, viewers, platform, config, log = console, limits, vi}),seo.structuredData.breadcrumbs(crumbsLd),].filter(Boolean) : null;-- send(req, res, 200, views.articlePage(v, { html: bodyHtml, discussion, actor: req.actor, mediaUrl: (id) => platform.media.publicUrl(id), resolve: (title) => infoboxLinks.get(title) || { href: null, exists: false }, flash: req.query.saved ? 'Saved.' : null }), {-- decision, title: v.revision.fields.title, description, ogType: 'article', jsonLd, path: svc.pagePath(space, page), cache: publicCache,++ const shareMedia = v.attachments.find((a) => a.mediaId && !a.brokenReason);++ send(req, res, 200, views.articlePage(v, { html: bodyHtml, discussion, actor: req.actor, mediaUrl: (id) => platform.media.publicUrl(id), resolve: (title) => infoboxLinks.get(title) || { href: null, exists: false }, flash: req.query.saved ? 'Saved.' : null, shareUrl: url, shareText: description, shareImage: shareMedia ? platform.media.publicUrl(shareMedia.mediaId) : null }), {++ decision, title: v.revision.fields.title, description, ogType: 'article', jsonLd, path: svc.pagePath(space, page), cache: publicCache, styles: ['kit.css'],});}));@@@ -4,6 +4,7 @@* unless raw()). Forms are plain HTML posts; nothing here needs JavaScript.*/const ssr = require('openvibe-publishing/ssr');++const kit = require('openvibe-shared/kit');const showcase = require('openvibe-shared/showcase');const { figureHtml } = require('openvibe-publishing/media');const { infoboxToText, mediaRefs } = require('../wiki/content');@@@ -408,8 +409,9 @@ function pageFacts(v, base) {return html`<p class="wk-facts">${facts.map((f, i) => html`${i ? html`<span aria-hidden="true"> · </span>` : ''}<span>${f}</span>`)}${badge ? html` ${badge}` : ''}</p>`;}--function articlePage(v, { html: contentHtml, discussion, actor, mediaUrl, resolve, flash }) {++function articlePage(v, { html: contentHtml, discussion, actor, mediaUrl, resolve, flash, shareUrl = '', shareText = '', shareImage = null }) {const { space, page, revision: rev } = v;++ const title = rev.fields.title || page.title;const base = wpath(space, page);const trail = [{ name: 'Wiki', url: '/' }, { name: space.name, url: `/s/${e(space.slug)}` }];if (v.parent) trail.push({ name: v.parent.title, url: wpath(space, v.parent) });@@@ -424,9 +426,10 @@ function articlePage(v, { html: contentHtml, discussion, actor, mediaUrl, resolvif (v.canEdit && v.decision && !v.decision.indexable && page.state === 'published') banners.push(html`<p class="wk-notice wk-info">Search engines are told not to index this page: ${v.decision.reasons.map((r) => r.code + (r.detail ? ` (${r.detail})` : '')).join(', ')}.</p>`);const actions = [html`<a href="${base}/history">History</a>`];if (v.canEdit) actions.unshift(html`<a href="${base}/edit">Edit</a>`), actions.push(html`<a href="${base}/settings">Page settings</a>`);++ if (shareUrl) actions.unshift(html`${raw(kit.share({ title, url: shareUrl, text: shareText || '', image: shareImage || null }))}`);return html`${crumbs(trail)}<article class="wk-article" data-page-id="${page.id}" data-revision="${String(rev.number)}">--<header><h1>${rev.fields.title || page.title}</h1><nav class="wk-actions" aria-label="Page actions">${actions.map((a, i) => html`${i ? ' · ' : ''}${a}`)}</nav>${pageFacts(v, base)}</header>++<header><h1>${title}</h1><nav class="wk-actions" aria-label="Page actions">${actions.map((a, i) => html`${i ? ' · ' : ''}${a}`)}</nav>${pageFacts(v, base)}</header>${banners}${raw(infoboxHtml(v.infobox, resolve, outRel(space), { media: (id) => { const a = v.attachments.find((x) => x.mediaId === id); return a && mediaUrl ? figureHtml(a, { urlFor: mediaUrl, unavailableText: UNAVAILABLE[a.brokenReason] || UNAVAILABLE.not_found }) : null; } }))}<div class="wk-content">${raw(contentHtml)}</div>nnew file mode 100644@@@ -0,0 +1,30 @@++'use strict';++/**++ * A wiki article carries the network's one Share button (openvibe-shared 4.114.0): one [data-ov-share-sheet] in the++ * page's own actions, with the article's canonical public address, its title and its summary. The navbar loads the++ * sheet on the first press, so the page itself loads nothing more.++ */++const assert = require('assert');++const H = require('./helpers');++++(async () => {++ const h = await H.boot();++ const me = { kind: 'user', subject: H.subject() };++ try {++ const space = await h.svc.createSpace({ name: 'Share demo', slug: 'share-demo' }, me);++ const { page } = await h.svc.createPage(space.id, {++ title: 'A shareable page', body: `The body of the page. ${H.LONG}`, summary: 'One line about the page.',++ }, me);++ await h.svc.publish(page.id, {}, me);++ const r = await H.req(h, 'GET', `/w/share-demo/${page.slug}`);++ assert.strictEqual(r.status, 200);++ assert.strictEqual((r.text.match(/data-ov-share-sheet/g) || []).length, 1, 'one Share button');++ assert.ok(r.text.includes('data-url="http://wiki.test/w/share-demo/a-shareable-page"'), 'the article address');++ assert.ok(r.text.includes('data-title="A shareable page"'), 'its title');++ assert.ok(r.text.includes('data-text="One line about the page."'), 'its summary');++ assert.ok(r.text.includes('class="ovk-btn ovk-btn--quiet"') && r.text.includes('<span>Share</span>'), 'the shared kit button');++ console.log('share sheet ok');++ } finally {++ await h.stop();++ }++})().catch((e) => { console.error(e); process.exit(1); });
@@ -2,6 +2,7 @@
@@ -2,6 +2,7 @@
## Unreleased
## Unreleased
+- Share: every article carries the network's one Share button (`data-ov-share-sheet`) in its page actions, with the article's canonical address, title and summary; `openvibe-shared`'s navbar loads the sheet on the first press. `test/share-sheet.test.js`.
- Account export and deletion (ADR-033): `POST /internal/events` (loopback, `WIKI_EVENTS_SECRET`) answers network.account.export_requested and network.account.deleted through `server/wiki/account-data.js` over `openvibe-sdk/account-data` (SDK v0.37.0, was v0.35.0).
- Account export and deletion (ADR-033): `POST /internal/events` (loopback, `WIKI_EVENTS_SECRET`) answers network.account.export_requested and network.account.deleted through `server/wiki/account-data.js` over `openvibe-sdk/account-data` (SDK v0.37.0, was v0.35.0).
- **Deleted:** the person's own user spaces (through `deleteSpace`), roles, watches and drafts.
- **Deleted:** the person's own user spaces (through `deleteSpace`), roles, watches and drafts.
- **Authorless:** their contributions elsewhere stay without their id.
- **Authorless:** their contributions elsewhere stay without their id.
@@ -415,8 +415,9 @@ function createPages({ svc, viewers, platform, config, log = console, limits, vi
@@ -415,8 +415,9 @@ function createPages({ svc, viewers, platform, config, log = console, limits, vi
}),
}),
seo.structuredData.breadcrumbs(crumbsLd),
seo.structuredData.breadcrumbs(crumbsLd),
].filter(Boolean) : null;
].filter(Boolean) : null;
- send(req, res, 200, views.articlePage(v, { html: bodyHtml, discussion, actor: req.actor, mediaUrl: (id) => platform.media.publicUrl(id), resolve: (title) => infoboxLinks.get(title) || { href: null, exists: false }, flash: req.query.saved ? 'Saved.' : null }), {
- decision, title: v.revision.fields.title, description, ogType: 'article', jsonLd, path: svc.pagePath(space, page), cache: publicCache,
+ const shareMedia = v.attachments.find((a) => a.mediaId && !a.brokenReason);
+ send(req, res, 200, views.articlePage(v, { html: bodyHtml, discussion, actor: req.actor, mediaUrl: (id) => platform.media.publicUrl(id), resolve: (title) => infoboxLinks.get(title) || { href: null, exists: false }, flash: req.query.saved ? 'Saved.' : null, shareUrl: url, shareText: description, shareImage: shareMedia ? platform.media.publicUrl(shareMedia.mediaId) : null }), {
+ decision, title: v.revision.fields.title, description, ogType: 'article', jsonLd, path: svc.pagePath(space, page), cache: publicCache, styles: ['kit.css'],
});
});
}));
}));
@@ -4,6 +4,7 @@
@@ -4,6 +4,7 @@
* unless raw()). Forms are plain HTML posts; nothing here needs JavaScript.
* unless raw()). Forms are plain HTML posts; nothing here needs JavaScript.
*/
*/
const ssr = require('openvibe-publishing/ssr');
const ssr = require('openvibe-publishing/ssr');
+const kit = require('openvibe-shared/kit');
const showcase = require('openvibe-shared/showcase');
const showcase = require('openvibe-shared/showcase');
const { figureHtml } = require('openvibe-publishing/media');
const { figureHtml } = require('openvibe-publishing/media');
const { infoboxToText, mediaRefs } = require('../wiki/content');
const { infoboxToText, mediaRefs } = require('../wiki/content');
@@ -408,8 +409,9 @@ function pageFacts(v, base) {
@@ -408,8 +409,9 @@ function pageFacts(v, base) {
return html`<p class="wk-facts">${facts.map((f, i) => html`${i ? html`<span aria-hidden="true"> · </span>` : ''}<span>${f}</span>`)}${badge ? html` ${badge}` : ''}</p>`;
return html`<p class="wk-facts">${facts.map((f, i) => html`${i ? html`<span aria-hidden="true"> · </span>` : ''}<span>${f}</span>`)}${badge ? html` ${badge}` : ''}</p>`;
}
}
-function articlePage(v, { html: contentHtml, discussion, actor, mediaUrl, resolve, flash }) {
+function articlePage(v, { html: contentHtml, discussion, actor, mediaUrl, resolve, flash, shareUrl = '', shareText = '', shareImage = null }) {
const { space, page, revision: rev } = v;
const { space, page, revision: rev } = v;
+ const title = rev.fields.title || page.title;
const base = wpath(space, page);
const base = wpath(space, page);
const trail = [{ name: 'Wiki', url: '/' }, { name: space.name, url: `/s/${e(space.slug)}` }];
const trail = [{ name: 'Wiki', url: '/' }, { name: space.name, url: `/s/${e(space.slug)}` }];
if (v.parent) trail.push({ name: v.parent.title, url: wpath(space, v.parent) });
if (v.parent) trail.push({ name: v.parent.title, url: wpath(space, v.parent) });
@@ -424,9 +426,10 @@ function articlePage(v, { html: contentHtml, discussion, actor, mediaUrl, resolv
@@ -424,9 +426,10 @@ function articlePage(v, { html: contentHtml, discussion, actor, mediaUrl, resolv
if (v.canEdit && v.decision && !v.decision.indexable && page.state === 'published') banners.push(html`<p class="wk-notice wk-info">Search engines are told not to index this page: ${v.decision.reasons.map((r) => r.code + (r.detail ? ` (${r.detail})` : '')).join(', ')}.</p>`);
if (v.canEdit && v.decision && !v.decision.indexable && page.state === 'published') banners.push(html`<p class="wk-notice wk-info">Search engines are told not to index this page: ${v.decision.reasons.map((r) => r.code + (r.detail ? ` (${r.detail})` : '')).join(', ')}.</p>`);
const actions = [html`<a href="${base}/history">History</a>`];
const actions = [html`<a href="${base}/history">History</a>`];
if (v.canEdit) actions.unshift(html`<a href="${base}/edit">Edit</a>`), actions.push(html`<a href="${base}/settings">Page settings</a>`);
if (v.canEdit) actions.unshift(html`<a href="${base}/edit">Edit</a>`), actions.push(html`<a href="${base}/settings">Page settings</a>`);
+ if (shareUrl) actions.unshift(html`${raw(kit.share({ title, url: shareUrl, text: shareText || '', image: shareImage || null }))}`);
return html`${crumbs(trail)}
return html`${crumbs(trail)}
<article class="wk-article" data-page-id="${page.id}" data-revision="${String(rev.number)}">
<article class="wk-article" data-page-id="${page.id}" data-revision="${String(rev.number)}">
-<header><h1>${rev.fields.title || page.title}</h1><nav class="wk-actions" aria-label="Page actions">${actions.map((a, i) => html`${i ? ' · ' : ''}${a}`)}</nav>${pageFacts(v, base)}</header>
+<header><h1>${title}</h1><nav class="wk-actions" aria-label="Page actions">${actions.map((a, i) => html`${i ? ' · ' : ''}${a}`)}</nav>${pageFacts(v, base)}</header>
${banners}
${banners}
${raw(infoboxHtml(v.infobox, resolve, outRel(space), { media: (id) => { const a = v.attachments.find((x) => x.mediaId === id); return a && mediaUrl ? figureHtml(a, { urlFor: mediaUrl, unavailableText: UNAVAILABLE[a.brokenReason] || UNAVAILABLE.not_found }) : null; } }))}
${raw(infoboxHtml(v.infobox, resolve, outRel(space), { media: (id) => { const a = v.attachments.find((x) => x.mediaId === id); return a && mediaUrl ? figureHtml(a, { urlFor: mediaUrl, unavailableText: UNAVAILABLE[a.brokenReason] || UNAVAILABLE.not_found }) : null; } }))}
<div class="wk-content">${raw(contentHtml)}</div>
<div class="wk-content">${raw(contentHtml)}</div>
new file mode 100644
new file mode 100644
@@ -0,0 +1,30 @@
@@ -0,0 +1,30 @@
+'use strict';
+/**
+ * A wiki article carries the network's one Share button (openvibe-shared 4.114.0): one [data-ov-share-sheet] in the
+ * page's own actions, with the article's canonical public address, its title and its summary. The navbar loads the
+ * sheet on the first press, so the page itself loads nothing more.
+ */
+const assert = require('assert');
+const H = require('./helpers');
+
+(async () => {
+ const h = await H.boot();
+ const me = { kind: 'user', subject: H.subject() };
+ try {
+ const space = await h.svc.createSpace({ name: 'Share demo', slug: 'share-demo' }, me);
+ const { page } = await h.svc.createPage(space.id, {
+ title: 'A shareable page', body: `The body of the page. ${H.LONG}`, summary: 'One line about the page.',
+ }, me);
+ await h.svc.publish(page.id, {}, me);
+ const r = await H.req(h, 'GET', `/w/share-demo/${page.slug}`);
+ assert.strictEqual(r.status, 200);
+ assert.strictEqual((r.text.match(/data-ov-share-sheet/g) || []).length, 1, 'one Share button');
+ assert.ok(r.text.includes('data-url="http://wiki.test/w/share-demo/a-shareable-page"'), 'the article address');
+ assert.ok(r.text.includes('data-title="A shareable page"'), 'its title');
+ assert.ok(r.text.includes('data-text="One line about the page."'), 'its summary');
+ assert.ok(r.text.includes('class="ovk-btn ovk-btn--quiet"') && r.text.includes('<span>Share</span>'), 'the shared kit button');
+ console.log('share sheet ok');
+ } finally {
+ await h.stop();
+ }
+})().catch((e) => { console.error(e); process.exit(1); });