feat: implement web share target (#980)

fixes #974
This commit is contained in:
Nolan Lawson 2019-02-13 18:38:44 -08:00 committed by GitHub
parent df6b75e994
commit 58d1f62b2b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 140 additions and 4 deletions

View File

@ -0,0 +1,11 @@
import { store } from '../_store/store'
import { importShowComposeDialog } from '../_components/dialog/asyncDialogs'
export async function showShareDialogIfNecessary () {
let { isUserLoggedIn, openShareDialog } = store.get()
store.set({ openShareDialog: false })
if (isUserLoggedIn && openShareDialog) {
let showComposeDialog = await importShowComposeDialog()
showComposeDialog()
}
}

View File

@ -7,9 +7,28 @@
import NotLoggedInHome from '../_components/NotLoggedInHome.html'
import { store } from '../_store/store.js'
import TimelineHomePage from '../_components/TimelineHomePage.html'
import { observe } from 'svelte-extras'
import { showShareDialogIfNecessary } from '../_actions/showShareDialogIfNecessary'
export default {
async oncreate () {
let observed = false
this.observe('currentVerifyCredentials', verifyCredentials => {
if (verifyCredentials && !observed) {
// when the verifyCredentials object is available, we can check to see
// if the user is trying to share something, then share it
observed = true
/* no await */ showShareDialogIfNecessary()
}
})
},
store: () => store,
computed: {
currentVerifyCredentials: ({ $currentVerifyCredentials }) => $currentVerifyCredentials
},
methods: {
observe
},
components: {
NotLoggedInHome,
TimelineHomePage

View File

@ -44,13 +44,15 @@ async function doRefreshInstanceDataAndStream (store, instanceName) {
async function refreshInstanceData (instanceName) {
// these are all low-priority
scheduleIdleTask(() => updateVerifyCredentialsForInstance(instanceName))
scheduleIdleTask(() => updateCustomEmojiForInstance(instanceName))
scheduleIdleTask(() => updateListsForInstance(instanceName))
scheduleIdleTask(() => updatePushSubscriptionForInstance(instanceName))
// this is the only critical one
await updateInstanceInfo(instanceName)
// these are the only critical ones
await Promise.all([
updateInstanceInfo(instanceName),
updateVerifyCredentialsForInstance(instanceName)
])
}
function stream (store, instanceName, currentInstanceInfo) {

View File

@ -46,7 +46,8 @@ const nonPersistedState = {
sensitivesShown: {},
spoilersShown: {},
statusModifications: {},
verifyCredentials: {}
verifyCredentials: {},
openShareDialog: false
}
const state = Object.assign({}, persistedState, nonPersistedState)

View File

@ -0,0 +1,7 @@
// Per the web share target spec: https://wicg.github.io/web-share-target/
// U+0020 (SPACE) characters are encoded as "+", due to the use of
// application/x-www-form-urlencoded encoding, not "%20" as might be expected.
export function decodeURIComponentWithPluses (text) {
return text.split('+').map(decodeURIComponent).join(' ')
}

27
src/routes/share.html Normal file
View File

@ -0,0 +1,27 @@
<!-- this is just used for the web share target API -->
<script>
import { store } from './_store/store'
import { goto } from '../../__sapper__/client'
import { decodeURIComponentWithPluses } from './_utils/decodeURIComponentWithPluses'
const SHARE_KEYS = ['title', 'text', 'url']
export default {
store: () => store,
oncreate () {
let params = new URLSearchParams(location.search)
let text = SHARE_KEYS
.map(key => params.get(key) && decodeURIComponentWithPluses(params.get(key)))
.filter(Boolean)
.join(' ')
this.store.set({ openShareDialog: true })
this.store.clearComposeData('dialog')
this.store.setComposeData('dialog', { text })
this.store.save()
goto('/', { replaceState: true })
}
}
</script>

View File

@ -6,6 +6,17 @@
"description": "Alternative web client for Mastodon, focused on speed and simplicity.",
"display": "standalone",
"start_url": "/?pwa=true",
"share_target": {
"action": "/share",
"method": "GET",
"enctype": "application/x-www-form-urlencoded",
"url_template": "/share?title={title}&text={text}&url={url}",
"params": {
"title": "title",
"text": "text",
"url": "url"
}
},
"icons": [
{
"src": "icon-48.png",

View File

@ -0,0 +1,58 @@
import {
closeDialogButton,
composeModalInput,
getUrl, goBack, modalDialog, notificationsNavButton
} from '../utils'
import { loginAsFoobar } from '../roles'
fixture`027-share-target.js`
.page`http://localhost:4002`
const SHARE_URL = 'http://localhost:4002/share?' +
'title=My+cool+title&' +
'text=This+is+a+bit+clich%C3%A9&' +
'url=http%3A%2F%2Fexample.com'
const SHARE_TEXT = 'My cool title This is a bit cliché http://example.com'
test('Share target works when page is not open', async t => {
await loginAsFoobar(t)
await t
.navigateTo('about:blank')
.navigateTo(SHARE_URL)
.expect(getUrl()).eql('http://localhost:4002/')
.expect(modalDialog.hasAttribute('aria-hidden')).notOk()
.expect(composeModalInput.value).eql(SHARE_TEXT)
})
test('Share target works when page is open', async t => {
await loginAsFoobar(t)
await t
.navigateTo(SHARE_URL)
.expect(getUrl()).eql('http://localhost:4002/')
.expect(modalDialog.hasAttribute('aria-hidden')).notOk()
.expect(composeModalInput.value).eql(SHARE_TEXT)
})
test('Share target page replaces itself in back nav history', async t => {
await loginAsFoobar(t)
await t
.click(notificationsNavButton)
.expect(getUrl()).contains('/notifications')
.navigateTo(SHARE_URL)
.expect(getUrl()).eql('http://localhost:4002/')
.expect(modalDialog.hasAttribute('aria-hidden')).notOk()
.expect(composeModalInput.value).eql(SHARE_TEXT)
.click(closeDialogButton)
.expect(modalDialog.exists).notOk()
await goBack()
await t
.expect(getUrl()).contains('/notifications')
})
test('Share target does nothing when not logged in', async t => {
await t
.navigateTo(SHARE_URL)
.expect(getUrl()).eql('http://localhost:4002/')
.expect(modalDialog.exists).notOk()
})