add option to copy link to clipboard (#289)

Fixes #288
This commit is contained in:
Nolan Lawson 2018-05-12 15:00:11 -07:00 committed by GitHub
parent fa4dd59f01
commit c0d0b4dd36
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 219 additions and 99 deletions

View file

@ -34,5 +34,6 @@ module.exports = [
{id: 'fa-pencil', src: 'node_modules/font-awesome-svg-png/white/svg/pencil.svg', title: 'Compose'},
{id: 'fa-times', src: 'node_modules/font-awesome-svg-png/white/svg/times.svg', title: 'Close'},
{id: 'fa-volume-off', src: 'node_modules/font-awesome-svg-png/white/svg/volume-off.svg', title: 'Mute'},
{id: 'fa-volume-up', src: 'node_modules/font-awesome-svg-png/white/svg/volume-up.svg', title: 'Unmute'}
{id: 'fa-volume-up', src: 'node_modules/font-awesome-svg-png/white/svg/volume-up.svg', title: 'Unmute'},
{id: 'fa-copy', src: 'node_modules/font-awesome-svg-png/white/svg/copy.svg', title: 'Copy'}
]

View file

@ -15,6 +15,7 @@
flex-direction: column;
align-items: center;
pointer-events: none;
z-index: 100000;
}
.toast-container {

View file

@ -29,3 +29,7 @@ export const importShowStatusOptionsDialog = () => import(
export const importShowVideoDialog = () => import(
/* webpackChunkName: 'showVideoDialog' */ './creators/showVideoDialog'
).then(mod => mod.default)
export const importShowCopyDialog = () => import(
/* webpackChunkName: 'showCopyDialog' */ './creators/showCopyDialog'
).then(mod => mod.default)

View file

@ -10,7 +10,7 @@
import ModalDialog from './ModalDialog.html'
import { store } from '../../../_store/store'
import GenericDialogList from './GenericDialogList.html'
import { importShowComposeDialog } from '../asyncDialogs'
import { importShowComposeDialog, importShowCopyDialog } from '../asyncDialogs'
import { createDialogId } from '../helpers/createDialogId'
import { show } from '../helpers/showDialog'
import { close } from '../helpers/closeDialog'
@ -26,7 +26,9 @@ export default {
id: createDialogId()
}),
computed: {
// begin account data copypasta
//
// begin copypasta (StatusOptionsDialog.html / AccountProfileOptionsDialog.html)
//
verifyCredentialsId: ({ verifyCredentials }) => verifyCredentials.id,
following: ({ relationship }) => relationship && relationship.following,
followRequested: ({ relationship }) => relationship && relationship.requested,
@ -42,23 +44,26 @@ export default {
? `Unfollow @${acct}`
: `Follow @${acct}`
},
followIcon: ({ following, followRequested }) => {
return following ? '#fa-user-times' : followRequested ? '#fa-hourglass' : '#fa-user-plus'
},
blockLabel: ({ blocking, acct }) => {
return blocking ? `Unblock @${acct}` : `Block @${acct}`
},
followIcon: ({ following, followRequested }) => (
following ? '#fa-user-times' : followRequested ? '#fa-hourglass' : '#fa-user-plus'
),
blockLabel: ({ blocking, acct }) => (
blocking ? `Unblock @${acct}` : `Block @${acct}`
),
blockIcon: ({ blocking }) => blocking ? '#fa-unlock' : '#fa-ban',
muteLabel: ({ muting, acct }) => {
return muting ? `Unmute @${acct}` : `Mute @${acct}`
},
muteLabel: ({ muting, acct }) => (
muting ? `Unmute @${acct}` : `Mute @${acct}`
),
muteIcon: ({ muting }) => muting ? '#fa-volume-up' : '#fa-volume-off',
// end account data copypasta
items: ({ blockLabel, blocking, blockIcon, muteLabel, muteIcon,
isUser: ({ accountId, verifyCredentialsId }) => accountId === verifyCredentialsId,
//
// end copypasta (StatusOptionsDialog.html / AccountProfileOptionsDialog.html)
//
items: ({
blockLabel, blocking, blockIcon, muteLabel, muteIcon,
followLabel, followIcon, following, followRequested,
accountId, verifyCredentialsId, acct }) => {
let isUser = accountId === verifyCredentialsId
return [
accountId, verifyCredentialsId, acct, isUser
}) => ([
!isUser && {
key: 'mention',
label: `Mention @${acct}`,
@ -78,10 +83,13 @@ export default {
key: 'mute',
label: muteLabel,
icon: muteIcon
},
{
key: 'copy',
label: 'Copy link to account',
icon: '#fa-copy'
}
].filter(Boolean)
}
].filter(Boolean))
},
methods: {
show,
@ -96,6 +104,8 @@ export default {
return this.onBlockClicked()
case 'mute':
return this.onMuteClicked()
case 'copy':
return this.onCopyClicked()
}
},
async onMentionClicked () {
@ -121,6 +131,12 @@ export default {
let { accountId, muting } = this.get()
this.close()
await setAccountMuted(accountId, !muting, true)
},
async onCopyClicked () {
let { account } = this.get()
let { url } = account
let showCopyDialog = await importShowCopyDialog()
showCopyDialog(url)
}
},
components: {

View file

@ -0,0 +1,64 @@
<ModalDialog
{id}
{label}
{title}
background="var(--main-bg)"
>
<form class="copy-dialog-form">
<input value={text}
ref:input
>
<button type="button" on:click="onClick()">
Copy
</button>
</form>
</ModalDialog>
<style>
.copy-dialog-form {
display: grid;
grid-template-rows: min-content min-content;
grid-template-columns: 1fr;
grid-gap: 10px;
padding: 10px 20px;
width: 400px;
max-width: calc(100vw - 40px);
}
</style>
<script>
import ModalDialog from './ModalDialog.html'
import { show } from '../helpers/showDialog'
import { close } from '../helpers/closeDialog'
import { oncreate as onCreateDialog } from '../helpers/onCreateDialog'
import { toast } from '../../../_utils/toast'
import { doubleRAF } from '../../../_utils/doubleRAF'
export default {
oncreate () {
onCreateDialog.call(this)
let { text } = this.get()
let { input } = this.refs
// double raf is to work around a11y-dialog trying to set the input
doubleRAF(() => {
input.focus()
input.setSelectionRange(0, text.length)
})
},
methods: {
show,
close,
onClick () {
let { input } = this.refs
input.select()
document.execCommand('copy')
toast.say('Copied to clipboard')
this.close()
}
},
data: () => ({
text: ''
}),
components: {
ModalDialog
}
}
</script>

View file

@ -18,24 +18,27 @@ import { oncreate } from '../helpers/onCreateDialog'
import { setAccountBlocked } from '../../../_actions/block'
import { setAccountMuted } from '../../../_actions/mute'
import { setStatusPinnedOrUnpinned } from '../../../_actions/pin'
import { importShowCopyDialog } from '../asyncDialogs'
export default {
oncreate,
computed: {
relationship: ({ $currentAccountRelationship }) => $currentAccountRelationship,
account: ({ $currentAccountProfile }) => $currentAccountProfile,
verifyCredentials: ({ $currentVerifyCredentials }) => $currentVerifyCredentials,
statusId: ({ status }) => status.id,
pinned: ({ status }) => status.pinned,
// begin account data copypasta
verifyCredentialsId: ({ verifyCredentials }) => verifyCredentials.id,
following: ({ relationship }) => relationship && relationship.following,
followRequested: ({ relationship }) => relationship && relationship.requested,
accountId: ({ account }) => account && account.id,
acct: ({ account }) => account.acct,
muting: ({ relationship }) => relationship.muting,
blocking: ({ relationship }) => relationship.blocking,
followLabel: ({ following, followRequested, account, acct }) => {
relationship: ({$currentAccountRelationship}) => $currentAccountRelationship,
account: ({$currentAccountProfile}) => $currentAccountProfile,
verifyCredentials: ({$currentVerifyCredentials}) => $currentVerifyCredentials,
statusId: ({status}) => status.id,
pinned: ({status}) => status.pinned,
//
// begin copypasta (StatusOptionsDialog.html / AccountProfileOptionsDialog.html)
//
verifyCredentialsId: ({verifyCredentials}) => verifyCredentials.id,
following: ({relationship}) => relationship && relationship.following,
followRequested: ({relationship}) => relationship && relationship.requested,
accountId: ({account}) => account && account.id,
acct: ({account}) => account.acct,
muting: ({relationship}) => relationship.muting,
blocking: ({relationship}) => relationship.blocking,
followLabel: ({following, followRequested, account, acct}) => {
if (typeof following === 'undefined' || !account) {
return ''
}
@ -43,23 +46,26 @@ export default {
? `Unfollow @${acct}`
: `Follow @${acct}`
},
followIcon: ({ following, followRequested }) => {
return following ? '#fa-user-times' : followRequested ? '#fa-hourglass' : '#fa-user-plus'
},
blockLabel: ({ blocking, acct }) => {
return blocking ? `Unblock @${acct}` : `Block @${acct}`
},
blockIcon: ({ blocking }) => blocking ? '#fa-unlock' : '#fa-ban',
muteLabel: ({ muting, acct }) => {
return muting ? `Unmute @${acct}` : `Mute @${acct}`
},
muteIcon: ({ muting }) => muting ? '#fa-volume-up' : '#fa-volume-off',
// end account data copypasta
isUser: ({ accountId, verifyCredentialsId }) => accountId === verifyCredentialsId,
pinLabel: ({ pinned, isUser }) => isUser ? (pinned ? 'Unpin from profile' : 'Pin to profile') : '',
items: ({ blockLabel, blocking, blockIcon, muteLabel, muteIcon, followLabel, followIcon,
following, followRequested, pinLabel, isUser }) => {
return [
followIcon: ({following, followRequested}) => (
following ? '#fa-user-times' : followRequested ? '#fa-hourglass' : '#fa-user-plus'
),
blockLabel: ({blocking, acct}) => (
blocking ? `Unblock @${acct}` : `Block @${acct}`
),
blockIcon: ({blocking}) => blocking ? '#fa-unlock' : '#fa-ban',
muteLabel: ({muting, acct}) => (
muting ? `Unmute @${acct}` : `Mute @${acct}`
),
muteIcon: ({muting}) => muting ? '#fa-volume-up' : '#fa-volume-off',
isUser: ({accountId, verifyCredentialsId}) => accountId === verifyCredentialsId,
//
// end copypasta (StatusOptionsDialog.html / AccountProfileOptionsDialog.html)
//
pinLabel: ({pinned, isUser}) => isUser ? (pinned ? 'Unpin from profile' : 'Pin to profile') : '',
items: ({
blockLabel, blocking, blockIcon, muteLabel, muteIcon, followLabel, followIcon,
following, followRequested, pinLabel, isUser
}) => ([
isUser && {
key: 'delete',
label: 'Delete',
@ -84,10 +90,13 @@ export default {
key: 'mute',
label: muteLabel,
icon: muteIcon
},
{
key: 'copy',
label: 'Copy link to toot',
icon: '#fa-copy'
}
].filter(Boolean)
}
].filter(Boolean))
},
components: {
ModalDialog,
@ -109,6 +118,8 @@ export default {
return this.onBlockClicked()
case 'mute':
return this.onMuteClicked()
case 'copy':
return this.onCopyClicked()
}
},
async onDeleteClicked () {
@ -135,6 +146,12 @@ export default {
let { accountId, muting } = this.get()
this.close()
await setAccountMuted(accountId, !muting, true)
},
async onCopyClicked () {
let { status } = this.get()
let { url } = status
let showCopyDialog = await importShowCopyDialog()
showCopyDialog(url)
}
}
}

View file

@ -0,0 +1,16 @@
import CopyDialog from '../components/CopyDialog.html'
import { createDialogElement } from '../helpers/createDialogElement'
import { createDialogId } from '../helpers/createDialogId'
export default function showCopyDialog (text) {
let dialog = new CopyDialog({
target: createDialogElement(),
data: {
id: createDialogId(),
label: 'Copy dialog',
title: 'Copy link',
text
}
})
dialog.show()
}

View file

@ -117,6 +117,7 @@ if (!localStorage.store_currentInstance) {
<symbol id="fa-times" viewBox="0 0 1792 1792"><title>Close</title><path d="M1490 1322q0 40-28 68l-136 136q-28 28-68 28t-68-28l-294-294-294 294q-28 28-68 28t-68-28l-136-136q-28-28-28-68t28-68l294-294-294-294q-28-28-28-68t28-68l136-136q28-28 68-28t68 28l294 294 294-294q28-28 68-28t68 28l136 136q28 28 28 68t-28 68l-294 294 294 294q28 28 28 68z"></path></symbol>
<symbol id="fa-volume-off" viewBox="0 0 1792 1792"><title>Mute</title><path d="M1280 352v1088q0 26-19 45t-45 19-45-19l-333-333H576q-26 0-45-19t-19-45V704q0-26 19-45t45-19h262l333-333q19-19 45-19t45 19 19 45z"></path></symbol>
<symbol id="fa-volume-up" viewBox="0 0 1792 1792"><title>Unmute</title><path d="M832 352v1088q0 26-19 45t-45 19-45-19l-333-333H128q-26 0-45-19t-19-45V704q0-26 19-45t45-19h262l333-333q19-19 45-19t45 19 19 45zm384 544q0 76-42.5 141.5T1061 1131q-10 5-25 5-26 0-45-18.5t-19-45.5q0-21 12-35.5t29-25 34-23 29-36 12-56.5-12-56.5-29-36-34-23-29-25-12-35.5q0-27 19-45.5t45-18.5q15 0 25 5 70 27 112.5 93t42.5 142zm256 0q0 153-85 282.5T1162 1367q-13 5-25 5-27 0-46-19t-19-45q0-39 39-59 56-29 76-44 74-54 115.5-135.5T1344 896t-41.5-173.5T1187 587q-20-15-76-44-39-20-39-59 0-26 19-45t45-19q13 0 26 5 140 59 225 188.5t85 282.5zm256 0q0 230-127 422.5T1263 1602q-13 5-26 5-26 0-45-19t-19-45q0-36 39-59 7-4 22.5-10.5t22.5-10.5q46-25 82-51 123-91 192-227t69-289-69-289-192-227q-36-26-82-51-7-4-22.5-10.5T1212 308q-39-23-39-59 0-26 19-45t45-19q13 0 26 5 211 91 338 283.5T1728 896z"></path></symbol>
<symbol id="fa-copy" viewBox="0 0 1792 1792"><title>Copy</title><path d="M1696 384q40 0 68 28t28 68v1216q0 40-28 68t-68 28H736q-40 0-68-28t-28-68v-288H96q-40 0-68-28t-28-68V640q0-40 20-88t48-76L476 68q28-28 76-48t88-20h416q40 0 68 28t28 68v328q68-40 128-40h416zm-544 213L853 896h299V597zM512 213L213 512h299V213zm196 647l316-316V128H640v416q0 40-28 68t-68 28H128v640h512v-256q0-40 20-88t48-76zm956 804V512h-384v416q0 40-28 68t-68 28H768v640h896z"></path></symbol>
</svg><!-- end insert svg here -->
</svg>
<!-- The application will be rendered inside this element,