Files
SolidScribe/client/src/pages/ShareTargetPage.vue
T

149 lines
3.9 KiB
Vue

<template>
<div class="ui container share-target">
<div class="ui basic center aligned segment">
<div v-if="status === 'working'">
<loading-icon message="Saving to Scratch Pad" />
</div>
<div v-else-if="status === 'error'">
<h3 class="ui header">
<i class="exclamation triangle icon"></i>
Couldn't save that
</h3>
<p>You may be offline. Your text is safe below &mdash; retry when you have a connection.</p>
<textarea class="shared-text" rows="8" readonly :value="pushText"></textarea>
<div class="ui buttons">
<div class="ui green button" v-on:click="save()">
<i class="redo icon"></i> Retry
</div>
<div class="ui basic button" v-on:click="discard()">
Discard
</div>
</div>
</div>
<div v-else>
<loading-icon message="Working" />
</div>
</div>
</div>
</template>
<script>
import { defineAsyncComponent } from 'vue'
import axios from 'axios'
// Shared text is parked here when the share arrives while logged out. The
// share's query string does not survive the bounce through /login, so a
// redirect param alone would lose it.
const STASH_KEY = 'pendingShareText'
// Guard against a pathological share wedging localStorage
const MAX_STASH = 100 * 1024
export default {
name: 'ShareTargetPage',
components: {
'loading-icon': defineAsyncComponent(() => import('@/components/LoadingIconComponent.vue')),
},
data(){
return {
status: 'working',
pushText: '',
}
},
// Deliberately NOT calling $root.loginGateway() the way other pages do --
// it would bounce away and discard the shared payload before we read it.
mounted(){
const text = this.composeSharedText()
if(!text){
this.$router.replace('/quick')
return
}
this.pushText = text
// getLoggedIn is reliable here: App.vue's beforeCreate populates the
// username from localStorage before any route component mounts.
if(!this.$store.getters.getLoggedIn){
this.stash(text)
this.$router.replace({ path: '/login', query: { redirect: '/share-target' } })
return
}
this.save()
},
methods: {
// Android routinely duplicates content across these fields -- the URL
// lands in both `text` and `url`, the page title in both `title` and
// `text`. Without the dedupe every shared link is saved twice.
composeSharedText(){
const query = this.$route.query
const parts = ['title', 'text', 'url']
.map(key => String(query[key] ?? '').trim())
.filter(value => value.length > 0)
.filter((value, index, all) => all.indexOf(value) === index)
if(parts.length > 0){ return parts.join('\n') }
// Nothing in the URL: this is the replay after logging in
return localStorage.getItem(STASH_KEY) || ''
},
stash(text){
if(text.length > MAX_STASH){ return }
localStorage.setItem(STASH_KEY, text)
},
save(){
this.status = 'working'
// Same call the paste button makes. The server prepends to the
// scratch pad, strips HTML and auto-links URLs, so nothing needs
// formatting here.
axios.post('/api/quick-note/update', { 'pushText': this.pushText })
.then(() => {
localStorage.removeItem(STASH_KEY)
this.$bus.$emit('notification', 'Saved To Scratch Pad')
this.$router.replace('/quick')
})
.catch(error => {
// Keep the stash: offline is the likely cause and the service
// worker means the app boots without a connection.
console.error('[ShareTarget] save failed:', error)
this.stash(this.pushText)
this.status = 'error'
})
},
discard(){
localStorage.removeItem(STASH_KEY)
this.$router.replace('/quick')
},
},
}
</script>
<style scoped>
.share-target {
padding-top: 3em;
}
.shared-text {
width: 100%;
margin: 1em 0;
padding: 0.75em;
border: 1px solid var(--border_color);
border-radius: 4px;
background-color: var(--small_element_bg_color);
color: var(--text_color);
font-family: inherit;
}
</style>