parent
df6b75e994
commit
58d1f62b2b
11
src/routes/_actions/showShareDialogIfNecessary.js
Normal file
11
src/routes/_actions/showShareDialogIfNecessary.js
Normal 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()
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,9 +7,28 @@
|
||||||
import NotLoggedInHome from '../_components/NotLoggedInHome.html'
|
import NotLoggedInHome from '../_components/NotLoggedInHome.html'
|
||||||
import { store } from '../_store/store.js'
|
import { store } from '../_store/store.js'
|
||||||
import TimelineHomePage from '../_components/TimelineHomePage.html'
|
import TimelineHomePage from '../_components/TimelineHomePage.html'
|
||||||
|
import { observe } from 'svelte-extras'
|
||||||
|
import { showShareDialogIfNecessary } from '../_actions/showShareDialogIfNecessary'
|
||||||
|
|
||||||
export default {
|
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,
|
store: () => store,
|
||||||
|
computed: {
|
||||||
|
currentVerifyCredentials: ({ $currentVerifyCredentials }) => $currentVerifyCredentials
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
observe
|
||||||
|
},
|
||||||
components: {
|
components: {
|
||||||
NotLoggedInHome,
|
NotLoggedInHome,
|
||||||
TimelineHomePage
|
TimelineHomePage
|
||||||
|
|
|
@ -44,13 +44,15 @@ async function doRefreshInstanceDataAndStream (store, instanceName) {
|
||||||
|
|
||||||
async function refreshInstanceData (instanceName) {
|
async function refreshInstanceData (instanceName) {
|
||||||
// these are all low-priority
|
// these are all low-priority
|
||||||
scheduleIdleTask(() => updateVerifyCredentialsForInstance(instanceName))
|
|
||||||
scheduleIdleTask(() => updateCustomEmojiForInstance(instanceName))
|
scheduleIdleTask(() => updateCustomEmojiForInstance(instanceName))
|
||||||
scheduleIdleTask(() => updateListsForInstance(instanceName))
|
scheduleIdleTask(() => updateListsForInstance(instanceName))
|
||||||
scheduleIdleTask(() => updatePushSubscriptionForInstance(instanceName))
|
scheduleIdleTask(() => updatePushSubscriptionForInstance(instanceName))
|
||||||
|
|
||||||
// this is the only critical one
|
// these are the only critical ones
|
||||||
await updateInstanceInfo(instanceName)
|
await Promise.all([
|
||||||
|
updateInstanceInfo(instanceName),
|
||||||
|
updateVerifyCredentialsForInstance(instanceName)
|
||||||
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
function stream (store, instanceName, currentInstanceInfo) {
|
function stream (store, instanceName, currentInstanceInfo) {
|
||||||
|
|
|
@ -46,7 +46,8 @@ const nonPersistedState = {
|
||||||
sensitivesShown: {},
|
sensitivesShown: {},
|
||||||
spoilersShown: {},
|
spoilersShown: {},
|
||||||
statusModifications: {},
|
statusModifications: {},
|
||||||
verifyCredentials: {}
|
verifyCredentials: {},
|
||||||
|
openShareDialog: false
|
||||||
}
|
}
|
||||||
|
|
||||||
const state = Object.assign({}, persistedState, nonPersistedState)
|
const state = Object.assign({}, persistedState, nonPersistedState)
|
||||||
|
|
7
src/routes/_utils/decodeURIComponentWithPluses.js
Normal file
7
src/routes/_utils/decodeURIComponentWithPluses.js
Normal 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
27
src/routes/share.html
Normal 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>
|
|
@ -6,6 +6,17 @@
|
||||||
"description": "Alternative web client for Mastodon, focused on speed and simplicity.",
|
"description": "Alternative web client for Mastodon, focused on speed and simplicity.",
|
||||||
"display": "standalone",
|
"display": "standalone",
|
||||||
"start_url": "/?pwa=true",
|
"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": [
|
"icons": [
|
||||||
{
|
{
|
||||||
"src": "icon-48.png",
|
"src": "icon-48.png",
|
||||||
|
|
58
tests/spec/027-share-target.js
Normal file
58
tests/spec/027-share-target.js
Normal 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()
|
||||||
|
})
|
Loading…
Reference in a new issue