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-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-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-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; flex-direction: column;
align-items: center; align-items: center;
pointer-events: none; pointer-events: none;
z-index: 100000;
} }
.toast-container { .toast-container {

View File

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

View File

@ -10,7 +10,7 @@
import ModalDialog from './ModalDialog.html' import ModalDialog from './ModalDialog.html'
import { store } from '../../../_store/store' import { store } from '../../../_store/store'
import GenericDialogList from './GenericDialogList.html' import GenericDialogList from './GenericDialogList.html'
import { importShowComposeDialog } from '../asyncDialogs' import { importShowComposeDialog, importShowCopyDialog } from '../asyncDialogs'
import { createDialogId } from '../helpers/createDialogId' import { createDialogId } from '../helpers/createDialogId'
import { show } from '../helpers/showDialog' import { show } from '../helpers/showDialog'
import { close } from '../helpers/closeDialog' import { close } from '../helpers/closeDialog'
@ -26,7 +26,9 @@ export default {
id: createDialogId() id: createDialogId()
}), }),
computed: { computed: {
// begin account data copypasta //
// begin copypasta (StatusOptionsDialog.html / AccountProfileOptionsDialog.html)
//
verifyCredentialsId: ({ verifyCredentials }) => verifyCredentials.id, verifyCredentialsId: ({ verifyCredentials }) => verifyCredentials.id,
following: ({ relationship }) => relationship && relationship.following, following: ({ relationship }) => relationship && relationship.following,
followRequested: ({ relationship }) => relationship && relationship.requested, followRequested: ({ relationship }) => relationship && relationship.requested,
@ -42,46 +44,52 @@ export default {
? `Unfollow @${acct}` ? `Unfollow @${acct}`
: `Follow @${acct}` : `Follow @${acct}`
}, },
followIcon: ({ following, followRequested }) => { followIcon: ({ following, followRequested }) => (
return following ? '#fa-user-times' : followRequested ? '#fa-hourglass' : '#fa-user-plus' following ? '#fa-user-times' : followRequested ? '#fa-hourglass' : '#fa-user-plus'
}, ),
blockLabel: ({ blocking, acct }) => { blockLabel: ({ blocking, acct }) => (
return blocking ? `Unblock @${acct}` : `Block @${acct}` blocking ? `Unblock @${acct}` : `Block @${acct}`
}, ),
blockIcon: ({ blocking }) => blocking ? '#fa-unlock' : '#fa-ban', blockIcon: ({ blocking }) => blocking ? '#fa-unlock' : '#fa-ban',
muteLabel: ({ muting, acct }) => { muteLabel: ({ muting, acct }) => (
return muting ? `Unmute @${acct}` : `Mute @${acct}` muting ? `Unmute @${acct}` : `Mute @${acct}`
}, ),
muteIcon: ({ muting }) => muting ? '#fa-volume-up' : '#fa-volume-off', muteIcon: ({ muting }) => muting ? '#fa-volume-up' : '#fa-volume-off',
// end account data copypasta isUser: ({ accountId, verifyCredentialsId }) => accountId === verifyCredentialsId,
items: ({ blockLabel, blocking, blockIcon, muteLabel, muteIcon, //
// end copypasta (StatusOptionsDialog.html / AccountProfileOptionsDialog.html)
//
items: ({
blockLabel, blocking, blockIcon, muteLabel, muteIcon,
followLabel, followIcon, following, followRequested, followLabel, followIcon, following, followRequested,
accountId, verifyCredentialsId, acct }) => { accountId, verifyCredentialsId, acct, isUser
let isUser = accountId === verifyCredentialsId }) => ([
return [ !isUser && {
!isUser && { key: 'mention',
key: 'mention', label: `Mention @${acct}`,
label: `Mention @${acct}`, icon: '#fa-comments'
icon: '#fa-comments' },
}, !isUser && !blocking && {
!isUser && !blocking && { key: 'follow',
key: 'follow', label: followLabel,
label: followLabel, icon: followIcon
icon: followIcon },
}, !isUser && {
!isUser && { key: 'block',
key: 'block', label: blockLabel,
label: blockLabel, icon: blockIcon
icon: blockIcon },
}, !isUser && !blocking && {
!isUser && !blocking && { key: 'mute',
key: 'mute', label: muteLabel,
label: muteLabel, icon: muteIcon
icon: muteIcon },
} {
key: 'copy',
].filter(Boolean) label: 'Copy link to account',
} icon: '#fa-copy'
}
].filter(Boolean))
}, },
methods: { methods: {
show, show,
@ -96,6 +104,8 @@ export default {
return this.onBlockClicked() return this.onBlockClicked()
case 'mute': case 'mute':
return this.onMuteClicked() return this.onMuteClicked()
case 'copy':
return this.onCopyClicked()
} }
}, },
async onMentionClicked () { async onMentionClicked () {
@ -121,6 +131,12 @@ export default {
let { accountId, muting } = this.get() let { accountId, muting } = this.get()
this.close() this.close()
await setAccountMuted(accountId, !muting, true) await setAccountMuted(accountId, !muting, true)
},
async onCopyClicked () {
let { account } = this.get()
let { url } = account
let showCopyDialog = await importShowCopyDialog()
showCopyDialog(url)
} }
}, },
components: { 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 { setAccountBlocked } from '../../../_actions/block'
import { setAccountMuted } from '../../../_actions/mute' import { setAccountMuted } from '../../../_actions/mute'
import { setStatusPinnedOrUnpinned } from '../../../_actions/pin' import { setStatusPinnedOrUnpinned } from '../../../_actions/pin'
import { importShowCopyDialog } from '../asyncDialogs'
export default { export default {
oncreate, oncreate,
computed: { computed: {
relationship: ({ $currentAccountRelationship }) => $currentAccountRelationship, relationship: ({$currentAccountRelationship}) => $currentAccountRelationship,
account: ({ $currentAccountProfile }) => $currentAccountProfile, account: ({$currentAccountProfile}) => $currentAccountProfile,
verifyCredentials: ({ $currentVerifyCredentials }) => $currentVerifyCredentials, verifyCredentials: ({$currentVerifyCredentials}) => $currentVerifyCredentials,
statusId: ({ status }) => status.id, statusId: ({status}) => status.id,
pinned: ({ status }) => status.pinned, pinned: ({status}) => status.pinned,
// begin account data copypasta //
verifyCredentialsId: ({ verifyCredentials }) => verifyCredentials.id, // begin copypasta (StatusOptionsDialog.html / AccountProfileOptionsDialog.html)
following: ({ relationship }) => relationship && relationship.following, //
followRequested: ({ relationship }) => relationship && relationship.requested, verifyCredentialsId: ({verifyCredentials}) => verifyCredentials.id,
accountId: ({ account }) => account && account.id, following: ({relationship}) => relationship && relationship.following,
acct: ({ account }) => account.acct, followRequested: ({relationship}) => relationship && relationship.requested,
muting: ({ relationship }) => relationship.muting, accountId: ({account}) => account && account.id,
blocking: ({ relationship }) => relationship.blocking, acct: ({account}) => account.acct,
followLabel: ({ following, followRequested, account, acct }) => { muting: ({relationship}) => relationship.muting,
blocking: ({relationship}) => relationship.blocking,
followLabel: ({following, followRequested, account, acct}) => {
if (typeof following === 'undefined' || !account) { if (typeof following === 'undefined' || !account) {
return '' return ''
} }
@ -43,51 +46,57 @@ export default {
? `Unfollow @${acct}` ? `Unfollow @${acct}`
: `Follow @${acct}` : `Follow @${acct}`
}, },
followIcon: ({ following, followRequested }) => { followIcon: ({following, followRequested}) => (
return following ? '#fa-user-times' : followRequested ? '#fa-hourglass' : '#fa-user-plus' following ? '#fa-user-times' : followRequested ? '#fa-hourglass' : '#fa-user-plus'
}, ),
blockLabel: ({ blocking, acct }) => { blockLabel: ({blocking, acct}) => (
return blocking ? `Unblock @${acct}` : `Block @${acct}` blocking ? `Unblock @${acct}` : `Block @${acct}`
}, ),
blockIcon: ({ blocking }) => blocking ? '#fa-unlock' : '#fa-ban', blockIcon: ({blocking}) => blocking ? '#fa-unlock' : '#fa-ban',
muteLabel: ({ muting, acct }) => { muteLabel: ({muting, acct}) => (
return muting ? `Unmute @${acct}` : `Mute @${acct}` muting ? `Unmute @${acct}` : `Mute @${acct}`
}, ),
muteIcon: ({ muting }) => muting ? '#fa-volume-up' : '#fa-volume-off', muteIcon: ({muting}) => muting ? '#fa-volume-up' : '#fa-volume-off',
// end account data copypasta isUser: ({accountId, verifyCredentialsId}) => accountId === verifyCredentialsId,
isUser: ({ accountId, verifyCredentialsId }) => accountId === verifyCredentialsId, //
pinLabel: ({ pinned, isUser }) => isUser ? (pinned ? 'Unpin from profile' : 'Pin to profile') : '', // end copypasta (StatusOptionsDialog.html / AccountProfileOptionsDialog.html)
items: ({ blockLabel, blocking, blockIcon, muteLabel, muteIcon, followLabel, followIcon, //
following, followRequested, pinLabel, isUser }) => { pinLabel: ({pinned, isUser}) => isUser ? (pinned ? 'Unpin from profile' : 'Pin to profile') : '',
return [ items: ({
isUser && { blockLabel, blocking, blockIcon, muteLabel, muteIcon, followLabel, followIcon,
key: 'delete', following, followRequested, pinLabel, isUser
label: 'Delete', }) => ([
icon: '#fa-trash' isUser && {
}, key: 'delete',
isUser && { label: 'Delete',
key: 'pin', icon: '#fa-trash'
label: pinLabel, },
icon: '#fa-thumb-tack' isUser && {
}, key: 'pin',
!isUser && !blocking && { label: pinLabel,
key: 'follow', icon: '#fa-thumb-tack'
label: followLabel, },
icon: followIcon !isUser && !blocking && {
}, key: 'follow',
!isUser && { label: followLabel,
key: 'block', icon: followIcon
label: blockLabel, },
icon: blockIcon !isUser && {
}, key: 'block',
!isUser && !blocking && { label: blockLabel,
key: 'mute', icon: blockIcon
label: muteLabel, },
icon: muteIcon !isUser && !blocking && {
} key: 'mute',
label: muteLabel,
].filter(Boolean) icon: muteIcon
} },
{
key: 'copy',
label: 'Copy link to toot',
icon: '#fa-copy'
}
].filter(Boolean))
}, },
components: { components: {
ModalDialog, ModalDialog,
@ -109,6 +118,8 @@ export default {
return this.onBlockClicked() return this.onBlockClicked()
case 'mute': case 'mute':
return this.onMuteClicked() return this.onMuteClicked()
case 'copy':
return this.onCopyClicked()
} }
}, },
async onDeleteClicked () { async onDeleteClicked () {
@ -135,6 +146,12 @@ export default {
let { accountId, muting } = this.get() let { accountId, muting } = this.get()
this.close() this.close()
await setAccountMuted(accountId, !muting, true) 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-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-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-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><!-- end insert svg here -->
</svg> </svg>
<!-- The application will be rendered inside this element, <!-- The application will be rendered inside this element,