From d044e12aee24a480e9ca327b175dadcd307bc924 Mon Sep 17 00:00:00 2001 From: Nolan Lawson Date: Sun, 21 Mar 2021 13:49:59 -0700 Subject: [PATCH] feat: add PWA shortcuts for compose/notifications (#2019) * feat: add PWA shortcuts for compose/notifications Fixes #2012 * fix: fix icon path --- ...logIfNecessary.js => showComposeDialog.js} | 19 ++++---- .../_store/observers/loggedInObservers.js | 4 +- ...rvers.js => showComposeDialogObservers.js} | 12 ++--- src/service-worker.js | 2 +- src/static/icon-shortcut-fa-bell.xcf | Bin 0 -> 16472 bytes src/static/icon-shortcut-fa-pencil.xcf | Bin 0 -> 17063 bytes static/icon-shortcut-fa-bell.png | Bin 0 -> 4246 bytes static/icon-shortcut-fa-pencil.png | Bin 0 -> 2501 bytes static/manifest.json | 26 +++++++++++ tests/spec/027-web-share-and-web-shortcuts.js | 44 ++++++++++++++++++ tests/utils.js | 44 ++++++++++++++++++ 11 files changed, 132 insertions(+), 19 deletions(-) rename src/routes/_actions/{showShareDialogIfNecessary.js => showComposeDialog.js} (66%) rename src/routes/_store/observers/{showShareDialogObservers.js => showComposeDialogObservers.js} (50%) create mode 100644 src/static/icon-shortcut-fa-bell.xcf create mode 100644 src/static/icon-shortcut-fa-pencil.xcf create mode 100644 static/icon-shortcut-fa-bell.png create mode 100644 static/icon-shortcut-fa-pencil.png create mode 100644 tests/spec/027-web-share-and-web-shortcuts.js diff --git a/src/routes/_actions/showShareDialogIfNecessary.js b/src/routes/_actions/showComposeDialog.js similarity index 66% rename from src/routes/_actions/showShareDialogIfNecessary.js rename to src/routes/_actions/showComposeDialog.js index f86aeed5..da167191 100644 --- a/src/routes/_actions/showShareDialogIfNecessary.js +++ b/src/routes/_actions/showComposeDialog.js @@ -3,24 +3,22 @@ import { importShowComposeDialog } from '../_components/dialog/asyncDialogs/impo import { database } from '../_database/database' import { doMediaUpload } from './media' -export async function showShareDialogIfNecessary () { +// show a compose dialog, typically invoked by the Web Share API or a PWA shortcut +export async function showComposeDialog () { const { isUserLoggedIn } = store.get() if (!isUserLoggedIn) { return } + const importShowComposeDialogPromise = importShowComposeDialog() // start promise early + const data = await database.getWebShareData() - if (!data) { - return + + if (data) { + await database.deleteWebShareData() // only need this data once; it came from Web Share (service worker) } - // delete from IDB and import the dialog in parallel - const [showComposeDialog] = await Promise.all([ - importShowComposeDialog(), - database.deleteWebShareData() - ]) - console.log('share data', data) - const { title, text, url, file } = data + const { title, text, url, file } = (data || {}) // url is currently ignored on Android, but one can dream // https://web.dev/web-share-target/#verifying-shared-content @@ -30,6 +28,7 @@ export async function showShareDialogIfNecessary () { store.setComposeData('dialog', { text: composeText }) store.save() + const showComposeDialog = await importShowComposeDialogPromise showComposeDialog() if (file) { // start the upload once the dialog is in view so it shows the loading spinner and everything /* no await */ doMediaUpload('dialog', file) diff --git a/src/routes/_store/observers/loggedInObservers.js b/src/routes/_store/observers/loggedInObservers.js index ec2232bc..99de70ba 100644 --- a/src/routes/_store/observers/loggedInObservers.js +++ b/src/routes/_store/observers/loggedInObservers.js @@ -7,7 +7,7 @@ import { customScrollbarObservers } from './customScrollbarObservers' import { customEmojiObservers } from './customEmojiObservers' import { cleanup } from './cleanup' import { wordFilterObservers } from './wordFilterObservers' -import { showShareDialogObservers } from './showShareDialogObservers' +import { showComposeDialogObservers } from './showComposeDialogObservers' import { badgeObservers } from './badgeObservers' // These observers can be lazy-loaded when the user is actually logged in. @@ -21,7 +21,7 @@ export function loggedInObservers () { notificationPermissionObservers() customScrollbarObservers() customEmojiObservers() - showShareDialogObservers() + showComposeDialogObservers() badgeObservers() cleanup() } diff --git a/src/routes/_store/observers/showShareDialogObservers.js b/src/routes/_store/observers/showComposeDialogObservers.js similarity index 50% rename from src/routes/_store/observers/showShareDialogObservers.js rename to src/routes/_store/observers/showComposeDialogObservers.js index afd8461f..2287ca2c 100644 --- a/src/routes/_store/observers/showShareDialogObservers.js +++ b/src/routes/_store/observers/showComposeDialogObservers.js @@ -1,18 +1,18 @@ import { store } from '../store' -import { showShareDialogIfNecessary } from '../../_actions/showShareDialogIfNecessary' +import { showComposeDialog } from '../../_actions/showComposeDialog' // If the user is logged in, and if the Service Worker handled a POST and set special data // in IndexedDB, then we want to handle it on the home page. -export function showShareDialogObservers () { +export function showComposeDialogObservers () { let observedOnce = false - store.observe('currentVerifyCredentials', verifyCredentials => { + store.observe('currentVerifyCredentials', async verifyCredentials => { if (verifyCredentials && !observedOnce) { // when the verifyCredentials object is available, we can check to see - // if the user is trying to share something, then share it + // if the user is trying to share something (or we got here from a shortcut), then share it observedOnce = true const { currentPage } = store.get() - if (currentPage === 'home') { - /* no await */ showShareDialogIfNecessary() + if (currentPage === 'home' && new URLSearchParams(location.search).get('compose') === 'true') { + await showComposeDialog() } } }) diff --git a/src/service-worker.js b/src/service-worker.js index 6fb5c8c2..ecb6f338 100644 --- a/src/service-worker.js +++ b/src/service-worker.js @@ -116,7 +116,7 @@ self.addEventListener('fetch', event => { await setWebShareData({ title, text, url, file }) await closeKeyValIDBConnection() // don't need to keep the IDB connection open return Response.redirect( - '/?pwa=true', // same as start_url in manifest.json. This can only be invoked from PWAs + '/?pwa=true&compose=true', // pwa=true because this can only be invoked from a PWA 303 // 303 recommended by https://web.dev/web-share-target/ ) } diff --git a/src/static/icon-shortcut-fa-bell.xcf b/src/static/icon-shortcut-fa-bell.xcf new file mode 100644 index 0000000000000000000000000000000000000000..1bd04c62ac1841234b5e5215af78b72a45dcbe51 GIT binary patch literal 16472 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 MXc5;%F9TZifBt(;*#H0l literal 0 HcmV?d00001 diff --git a/src/static/icon-shortcut-fa-pencil.xcf b/src/static/icon-shortcut-fa-pencil.xcf new file mode 100644 index 0000000000000000000000000000000000000000..7a5a56635b5435c5c1d09e88f0437f7256928d24 GIT binary patch literal 17063 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 literal 0 HcmV?d00001 diff --git a/static/icon-shortcut-fa-pencil.png b/static/icon-shortcut-fa-pencil.png new file mode 100644 index 0000000000000000000000000000000000000000..5863c70568b3845a8a065e064209e7443526da79 GIT binary patch literal 2501 zcmd^=`8$-27sj7KhGK}agb;-`SxQ7>E2D_lR#P!jGT{xSL>NoMn^(#kY9woflwFD% zLn2LA~>c|MyX20=(H27j}}lrc_a!?Jti^ogs} z9X)G85ePvL^S7wv<qZ8cfCdLX=H29re)x>yQ{_AZg z^F=d*Hp`!*3%*j=LnCZ~K(fjdIfc>&zOg5#c^~@Lre=6rT^2U$m-dbJI^_)@4tRv8 z7d3qxnO)(Ep$&(Ev)g92`DJwrTr$y0N~cH&JI4eU}RD%>;s@I({b;e z`q(nDt9I%od5X2gcUW<_L5ISUHfP#V>h}!~9`q%>an)437QFFCOKq z7Mo^~{q~CBSxeQ1=YyQC-ba~EleB?~ShOJR2Ej-?(4reZRv*!-3|Dtnkg-<)45o|@ z6%sThVbyorqIssJhfA^zvx@UBTPx0?H&TG}S97R-x2Q^e+DFJw)$y-XTUOG81{9rEUEZup%+BdL1D*ED zfCp*upUtRMJ|!hBhXDE3x6a3!OeI97*KY|@+#gvO)ZSIEY+@{{eS%PWtux!pq()5} z`*b?4tHrFULz`-Ow3?|He&yi_a++ef_q~9+y}rA>RMqfqzSlZb)T`X0&ji|jU)$5z z=+g^+Aas))&6MvPLG2{&S;-lSbz`WP_W$7d9=NVW;uz)1R7LN4@>xBjyrC1- zDLGk6^r=2*;}wylXoTQm?t;|tp{)$Whv1&&iA01 zSqB0<2xW7iVDp{(`W5(*eDz-g?vmD2Bo`OIb$GzS>d39R_hjARM;nTDuQ9vC6x}?I zsXP$HToZ4Gyl$d51M^d1PA}1VpA=;r>hfIA^^nO-4GRmqj$35xJjDhfifnrM&(N*k z8_@ni2m4b$CFRA6Djf^ol@S6}76_$NHY8D^}NeVE7F?A>dgTr4cy9K6pahAlI)j<5N<53x~FS@I?LOk44SEFYR?yDMrJKk0#=rq@E&|lJ6XW8s*vG zB`Mc*FhrFuAvpJyLj;*PT*ZSHp;!;xYU@uVl;m5Q?_V(0ao(9)lYo07-_ZMm_nM%W z-u}n@Hf}wF!wb;IpCy&D1TL-Mreitn+z|O>3aH@89W4sxGdwy|ptG5v^N$F~T&h;0 zSv=c}JZT)q7}&8fK-8GjP65dV3(d&uz3XVNd4R|wwTS|{8O${!y;1APP-Pv#zKNDG zO2z#baU-xw4s|_Es4X`{F)8%E1pQL*#isH0+4()LiEUMDnEaAF=41`lPq1n7si;x& zwq=zVuRH6J4mN}iR{mDM3JN8?CB(hAEwKX_(wtz=(jhGGJ5dMTWOBaYFeo+7?2OnD1K(O!YNl|}&23Azno9l# z8DXlQvR;1m=oL?T>TTg0Uc2?4!;?2+ZF%vz>9sL9Bt|Q9S~RXZZyz70ZHrT3+u$x^ z;>9|2C8={zm))8mp%^K*SF$Efe2fKnTD|&jCvcZjyni?4_I_b5+?PDL`(NyP%g( zpuk$Jh_SK`ORMS@wXQI^j&?QWXlon__V}1ek_!_wV0_Ggs75((W^1%5^cdE<^!AB;+e`|=m(NF%9O|M0k zldS)~-YT>>?HYA6^BHu*&mR={ei}~2_`etZ=8sc&xYsf2#}OY60KnE=E(n&H1sn78 zG;_J2Ml-_TGtzSkbB5{R%(vPvHqZfcH#fRv;1;^|d@Ra!5_V(Hx(LqsaZ@| zObzxvKWsEp5&Qf;*LN{4IYLi}Nb|DVw|nH!L4_*gJwBDYH))*BpQ}U_mfP>hP!+#6 z%u32yfa`-!8vO0bjp04r$S9pLseQdYEIg4NchrzR8~S$c<6M)~`7{nj+S1fd z^>if~IYgALU9x#%n)iZpJbd%(pvHiG>Z~8p=G&96>6Q^+=Nl_T++P3Efa|eS&aWH; GQ~n3510#C? literal 0 HcmV?d00001 diff --git a/static/manifest.json b/static/manifest.json index eda10151..99eaa855 100644 --- a/static/manifest.json +++ b/static/manifest.json @@ -112,6 +112,32 @@ "purpose": "maskable" } ], + "shortcuts": [ + { + "name": "Write a toot", + "short_name": "New toot", + "description": "Start composing a new toot", + "url": "/?pwa=true&compose=true", + "icons": [ + { + "src": "/icon-shortcut-fa-pencil.png", + "sizes": "192x192" + } + ] + }, + { + "name": "View notifications", + "short_name": "Notifications", + "description": "View your new notifications", + "url": "/notifications?pwa=true", + "icons": [ + { + "src": "/icon-shortcut-fa-bell.png", + "sizes": "192x192" + } + ] + } + ], "screenshots": [ { "src": "screenshot-540-720-1.png", diff --git a/tests/spec/027-web-share-and-web-shortcuts.js b/tests/spec/027-web-share-and-web-shortcuts.js new file mode 100644 index 00000000..0ba28678 --- /dev/null +++ b/tests/spec/027-web-share-and-web-shortcuts.js @@ -0,0 +1,44 @@ +import { + composeModalInput, getComposeModalNthMediaListItem, + getUrl, modalDialogContents, simulateWebShare +} from '../utils' +import { loginAsFoobar } from '../roles' +import { ONE_TRANSPARENT_PIXEL } from '../../src/routes/_static/media' + +fixture`027-web-share-and-web-shortcuts.js` + .page`http://localhost:4002` + +test('Can take a shortcut directly to a compose dialog', async t => { + await loginAsFoobar(t) + await t + .expect(getUrl()).eql('http://localhost:4002/') + .navigateTo('http://localhost:4002/?compose=true') + .expect(modalDialogContents.exists).ok() + .expect(composeModalInput.value).eql('') + .expect(getComposeModalNthMediaListItem(1).exists).notOk() +}) + +test('Can share title/text using Web Share', async t => { + await loginAsFoobar(t) + await t + .expect(getUrl()).eql('http://localhost:4002/') + await (simulateWebShare({ title: 'my title', url: undefined, text: 'my text' })()) + await t + .navigateTo('http://localhost:4002/?compose=true') + .expect(modalDialogContents.exists).ok() + .expect(composeModalInput.value).eql('my title\n\nmy text') + .expect(getComposeModalNthMediaListItem(1).exists).notOk() +}) + +test('Can share a file using Web Share', async t => { + await loginAsFoobar(t) + await t + .expect(getUrl()).eql('http://localhost:4002/') + await (simulateWebShare({ title: undefined, url: undefined, text: undefined, file: ONE_TRANSPARENT_PIXEL })()) + await t + .navigateTo('http://localhost:4002/?compose=true') + .expect(modalDialogContents.exists).ok() + .expect(composeModalInput.value).eql('') + .expect(getComposeModalNthMediaListItem(1).exists).ok() + .expect(getComposeModalNthMediaListItem(1).getAttribute('aria-label')).eql('media') +}) diff --git a/tests/utils.js b/tests/utils.js index 3c21394d..22857142 100644 --- a/tests/utils.js +++ b/tests/utils.js @@ -265,6 +265,50 @@ export const uploadKittenImage = i => (exec(() => { } })) +export const simulateWebShare = ({ title, text, url, file }) => (exec(() => { + let blob + return Promise.resolve().then(() => { + if (file) { + return fetch(file).then(resp => resp.blob()).then(theBlob => { + blob = theBlob + }) + } + }).then(() => { + return new Promise((resolve, reject) => { + const request = indexedDB.open('keyval-store') + request.onerror = (event) => { + console.error(event) + reject(new Error('idb error')) + } + request.onupgradeneeded = () => { + request.result.createObjectStore('keyval') + } + request.onsuccess = (event) => { + const db = event.target.result + const txn = db.transaction('keyval', 'readwrite') + txn.onerror = () => reject(new Error('idb error')) + txn.oncomplete = () => { + db.close() + resolve() + } + txn.objectStore('keyval').put({ + title, + text, + url, + file: blob + }, 'web-share-data') + } + }) + }) +}, { + dependencies: { + title, + text, + url, + file + } +})) + export const focus = (selector) => (exec(() => { document.querySelector(selector).focus() }, {