mirror of
https://github.com/hcengineering/platform.git
synced 2025-04-16 05:13:06 +00:00
Fix copying text to clipboard for Safari
Signed-off-by: Alexander Onnikov <alexander.onnikov@xored.com>
This commit is contained in:
parent
098aff603d
commit
4a0cdb4cd7
@ -702,9 +702,9 @@ export function createModel (builder: Builder): void {
|
|||||||
createAction(
|
createAction(
|
||||||
builder,
|
builder,
|
||||||
{
|
{
|
||||||
action: recruit.actionImpl.CopyToClipboard,
|
action: view.actionImpl.CopyTextToClipboard,
|
||||||
actionProps: {
|
actionProps: {
|
||||||
type: 'id'
|
textProvider: recruit.function.GetApplicationId
|
||||||
},
|
},
|
||||||
label: recruit.string.CopyId,
|
label: recruit.string.CopyId,
|
||||||
icon: recruit.icon.Application,
|
icon: recruit.icon.Application,
|
||||||
@ -723,9 +723,9 @@ export function createModel (builder: Builder): void {
|
|||||||
createAction(
|
createAction(
|
||||||
builder,
|
builder,
|
||||||
{
|
{
|
||||||
action: recruit.actionImpl.CopyToClipboard,
|
action: view.actionImpl.CopyTextToClipboard,
|
||||||
actionProps: {
|
actionProps: {
|
||||||
type: 'link'
|
textProvider: recruit.function.GetApplicationLink
|
||||||
},
|
},
|
||||||
label: recruit.string.CopyLink,
|
label: recruit.string.CopyLink,
|
||||||
icon: recruit.icon.Application,
|
icon: recruit.icon.Application,
|
||||||
@ -744,9 +744,9 @@ export function createModel (builder: Builder): void {
|
|||||||
createAction(
|
createAction(
|
||||||
builder,
|
builder,
|
||||||
{
|
{
|
||||||
action: recruit.actionImpl.CopyToClipboard,
|
action: view.actionImpl.CopyTextToClipboard,
|
||||||
actionProps: {
|
actionProps: {
|
||||||
type: 'link'
|
textProvider: recruit.function.GetRecruitLink
|
||||||
},
|
},
|
||||||
label: recruit.string.CopyLink,
|
label: recruit.string.CopyLink,
|
||||||
icon: recruit.icon.Application,
|
icon: recruit.icon.Application,
|
||||||
|
@ -32,12 +32,16 @@ export default mergeIds(recruitId, recruit, {
|
|||||||
CopyCandidateLink: '' as Ref<Action>
|
CopyCandidateLink: '' as Ref<Action>
|
||||||
},
|
},
|
||||||
actionImpl: {
|
actionImpl: {
|
||||||
CreateOpinion: '' as ViewAction,
|
CreateOpinion: '' as ViewAction
|
||||||
CopyToClipboard: '' as ViewAction
|
|
||||||
},
|
},
|
||||||
category: {
|
category: {
|
||||||
Recruit: '' as Ref<ActionCategory>
|
Recruit: '' as Ref<ActionCategory>
|
||||||
},
|
},
|
||||||
|
function: {
|
||||||
|
GetApplicationId: '' as Resource<(doc: Doc, props: Record<string, any>) => Promise<string>>,
|
||||||
|
GetApplicationLink: '' as Resource<(doc: Doc, props: Record<string, any>) => Promise<string>>,
|
||||||
|
GetRecruitLink: '' as Resource<(doc: Doc, props: Record<string, any>) => Promise<string>>
|
||||||
|
},
|
||||||
string: {
|
string: {
|
||||||
ApplicationShort: '' as IntlString,
|
ApplicationShort: '' as IntlString,
|
||||||
ApplicationsShort: '' as IntlString,
|
ApplicationsShort: '' as IntlString,
|
||||||
|
@ -1129,9 +1129,9 @@ export function createModel (builder: Builder): void {
|
|||||||
createAction(
|
createAction(
|
||||||
builder,
|
builder,
|
||||||
{
|
{
|
||||||
action: tracker.actionImpl.CopyToClipboard,
|
action: view.actionImpl.CopyTextToClipboard,
|
||||||
actionProps: {
|
actionProps: {
|
||||||
type: 'id'
|
textProvider: tracker.function.GetIssueId
|
||||||
},
|
},
|
||||||
label: tracker.string.CopyIssueId,
|
label: tracker.string.CopyIssueId,
|
||||||
icon: tracker.icon.CopyID,
|
icon: tracker.icon.CopyID,
|
||||||
@ -1150,9 +1150,9 @@ export function createModel (builder: Builder): void {
|
|||||||
createAction(
|
createAction(
|
||||||
builder,
|
builder,
|
||||||
{
|
{
|
||||||
action: tracker.actionImpl.CopyToClipboard,
|
action: view.actionImpl.CopyTextToClipboard,
|
||||||
actionProps: {
|
actionProps: {
|
||||||
type: 'title'
|
textProvider: tracker.function.GetIssueTitle
|
||||||
},
|
},
|
||||||
label: tracker.string.CopyIssueTitle,
|
label: tracker.string.CopyIssueTitle,
|
||||||
icon: tracker.icon.CopyBranch,
|
icon: tracker.icon.CopyBranch,
|
||||||
@ -1171,9 +1171,9 @@ export function createModel (builder: Builder): void {
|
|||||||
createAction(
|
createAction(
|
||||||
builder,
|
builder,
|
||||||
{
|
{
|
||||||
action: tracker.actionImpl.CopyToClipboard,
|
action: view.actionImpl.CopyTextToClipboard,
|
||||||
actionProps: {
|
actionProps: {
|
||||||
type: 'link'
|
textProvider: tracker.function.GetIssueLink
|
||||||
},
|
},
|
||||||
label: tracker.string.CopyIssueUrl,
|
label: tracker.string.CopyIssueUrl,
|
||||||
icon: tracker.icon.CopyURL,
|
icon: tracker.icon.CopyURL,
|
||||||
|
@ -121,7 +121,8 @@
|
|||||||
} else {
|
} else {
|
||||||
location.path.length = 4
|
location.path.length = 4
|
||||||
}
|
}
|
||||||
await copyTextToClipboard(`${window.location.origin}${locationToUrl(location)}`)
|
const text = `${window.location.origin}${locationToUrl(location)}`
|
||||||
|
await copyTextToClipboard(text)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,7 +50,7 @@ import VacancyItemPresenter from './components/VacancyItemPresenter.svelte'
|
|||||||
import VacancyModifiedPresenter from './components/VacancyModifiedPresenter.svelte'
|
import VacancyModifiedPresenter from './components/VacancyModifiedPresenter.svelte'
|
||||||
import VacancyPresenter from './components/VacancyPresenter.svelte'
|
import VacancyPresenter from './components/VacancyPresenter.svelte'
|
||||||
import recruit from './plugin'
|
import recruit from './plugin'
|
||||||
import { copyToClipboard, getApplicationTitle } from './utils'
|
import { objectIdProvider, objectLinkProvider, getApplicationTitle } from './utils'
|
||||||
import VacancyList from './components/VacancyList.svelte'
|
import VacancyList from './components/VacancyList.svelte'
|
||||||
|
|
||||||
async function createOpinion (object: Doc): Promise<void> {
|
async function createOpinion (object: Doc): Promise<void> {
|
||||||
@ -254,8 +254,7 @@ async function noneApplicant (filter: Filter, onUpdate: () => void): Promise<Obj
|
|||||||
|
|
||||||
export default async (): Promise<Resources> => ({
|
export default async (): Promise<Resources> => ({
|
||||||
actionImpl: {
|
actionImpl: {
|
||||||
CreateOpinion: createOpinion,
|
CreateOpinion: createOpinion
|
||||||
CopyToClipboard: copyToClipboard
|
|
||||||
},
|
},
|
||||||
validator: {
|
validator: {
|
||||||
ApplicantValidator: applicantValidator
|
ApplicantValidator: applicantValidator
|
||||||
@ -305,6 +304,9 @@ export default async (): Promise<Resources> => ({
|
|||||||
ApplicationTitleProvider: getApplicationTitle,
|
ApplicationTitleProvider: getApplicationTitle,
|
||||||
HasActiveApplicant: hasActiveApplicant,
|
HasActiveApplicant: hasActiveApplicant,
|
||||||
HasNoActiveApplicant: hasNoActiveApplicant,
|
HasNoActiveApplicant: hasNoActiveApplicant,
|
||||||
NoneApplications: noneApplicant
|
NoneApplications: noneApplicant,
|
||||||
|
GetApplicationId: objectIdProvider,
|
||||||
|
GetApplicationLink: objectLinkProvider,
|
||||||
|
GetRecruitLink: objectLinkProvider
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import core, { Doc, Ref, TxOperations } from '@hcengineering/core'
|
import core, { Doc, Ref, TxOperations } from '@hcengineering/core'
|
||||||
import { translate } from '@hcengineering/platform'
|
import { translate } from '@hcengineering/platform'
|
||||||
import { copyTextToClipboard, getClient } from '@hcengineering/presentation'
|
import { getClient } from '@hcengineering/presentation'
|
||||||
import { Applicant, Candidate } from '@hcengineering/recruit'
|
import { Applicant, Candidate } from '@hcengineering/recruit'
|
||||||
import { getPanelURI } from '@hcengineering/ui'
|
import { getPanelURI } from '@hcengineering/ui'
|
||||||
import view from '@hcengineering/view'
|
import view from '@hcengineering/view'
|
||||||
@ -19,23 +19,13 @@ export async function getApplicationTitle (client: TxOperations, ref: Ref<Doc>):
|
|||||||
return `${label}-${object.number}`
|
return `${label}-${object.number}`
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function copyToClipboard (
|
export async function objectIdProvider (doc: Applicant | Candidate): Promise<string> {
|
||||||
object: Applicant | Candidate,
|
|
||||||
ev: Event,
|
|
||||||
{ type }: { type: string }
|
|
||||||
): Promise<void> {
|
|
||||||
const client = getClient()
|
const client = getClient()
|
||||||
let text: string
|
return await getApplicationTitle(client, doc._id)
|
||||||
switch (type) {
|
|
||||||
case 'id':
|
|
||||||
text = await getApplicationTitle(client, object._id)
|
|
||||||
break
|
|
||||||
case 'link':
|
|
||||||
// TODO: fix when short link is available
|
|
||||||
text = `${window.location.href}#${getPanelURI(view.component.EditDoc, object._id, object._class, 'content')}`
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
await copyTextToClipboard(text)
|
|
||||||
|
export async function objectLinkProvider (doc: Applicant | Candidate): Promise<string> {
|
||||||
|
return await Promise.resolve(
|
||||||
|
`${window.location.href}#${getPanelURI(view.component.EditDoc, doc._id, doc._class, 'content')}`
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
@ -59,7 +59,14 @@ import SetDueDateActionPopup from './components/SetDueDateActionPopup.svelte'
|
|||||||
import SetParentIssueActionPopup from './components/SetParentIssueActionPopup.svelte'
|
import SetParentIssueActionPopup from './components/SetParentIssueActionPopup.svelte'
|
||||||
import Views from './components/views/Views.svelte'
|
import Views from './components/views/Views.svelte'
|
||||||
import Statuses from './components/workflow/Statuses.svelte'
|
import Statuses from './components/workflow/Statuses.svelte'
|
||||||
import { copyToClipboard, getIssueId, getIssueTitle, resolveLocation } from './issues'
|
import {
|
||||||
|
getIssueId,
|
||||||
|
getIssueTitle,
|
||||||
|
issueIdProvider,
|
||||||
|
issueLinkProvider,
|
||||||
|
issueTitleProvider,
|
||||||
|
resolveLocation
|
||||||
|
} from './issues'
|
||||||
import tracker from './plugin'
|
import tracker from './plugin'
|
||||||
|
|
||||||
import SprintEditor from './components/sprints/SprintEditor.svelte'
|
import SprintEditor from './components/sprints/SprintEditor.svelte'
|
||||||
@ -212,10 +219,12 @@ export default async (): Promise<Resources> => ({
|
|||||||
await queryIssue(tracker.class.Issue, client, query, filter)
|
await queryIssue(tracker.class.Issue, client, query, filter)
|
||||||
},
|
},
|
||||||
function: {
|
function: {
|
||||||
IssueTitleProvider: getIssueTitle
|
IssueTitleProvider: getIssueTitle,
|
||||||
|
GetIssueId: issueIdProvider,
|
||||||
|
GetIssueLink: issueLinkProvider,
|
||||||
|
GetIssueTitle: issueTitleProvider
|
||||||
},
|
},
|
||||||
actionImpl: {
|
actionImpl: {
|
||||||
CopyToClipboard: copyToClipboard,
|
|
||||||
EditWorkflowStatuses: editWorkflowStatuses
|
EditWorkflowStatuses: editWorkflowStatuses
|
||||||
},
|
},
|
||||||
resolver: {
|
resolver: {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { Doc, DocumentUpdate, Ref, RelatedDocument, TxOperations } from '@hcengineering/core'
|
import { Doc, DocumentUpdate, Ref, RelatedDocument, TxOperations } from '@hcengineering/core'
|
||||||
import { copyTextToClipboard, getClient } from '@hcengineering/presentation'
|
import { getClient } from '@hcengineering/presentation'
|
||||||
import { Issue, Project, Sprint, Team, trackerId } from '@hcengineering/tracker'
|
import { Issue, Project, Sprint, Team, trackerId } from '@hcengineering/tracker'
|
||||||
import { getCurrentLocation, getPanelURI, Location } from '@hcengineering/ui'
|
import { getCurrentLocation, getPanelURI, Location } from '@hcengineering/ui'
|
||||||
import { workbenchId } from '@hcengineering/workbench'
|
import { workbenchId } from '@hcengineering/workbench'
|
||||||
@ -31,24 +31,18 @@ export function generateIssuePanelUri (issue: Issue): string {
|
|||||||
return getPanelURI(tracker.component.EditIssue, issue._id, issue._class, 'content')
|
return getPanelURI(tracker.component.EditIssue, issue._id, issue._class, 'content')
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function copyToClipboard (object: Issue, ev: Event, { type }: { type: string }): Promise<void> {
|
export async function issueIdProvider (doc: Doc): Promise<string> {
|
||||||
const client = getClient()
|
const client = getClient()
|
||||||
let text: string
|
return await getIssueTitle(client, doc._id)
|
||||||
switch (type) {
|
|
||||||
case 'id':
|
|
||||||
text = await getIssueTitle(client, object._id)
|
|
||||||
break
|
|
||||||
case 'title':
|
|
||||||
text = object.title
|
|
||||||
break
|
|
||||||
case 'link':
|
|
||||||
text = generateIssueShortLink(await getIssueTitle(client, object._id))
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await copyTextToClipboard(text)
|
export async function issueTitleProvider (doc: Issue): Promise<string> {
|
||||||
|
return await Promise.resolve(doc.title)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function issueLinkProvider (doc: Doc): Promise<string> {
|
||||||
|
const client = getClient()
|
||||||
|
return await getIssueTitle(client, doc._id).then(generateIssueShortLink)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function generateIssueShortLink (issueId: string): string {
|
export function generateIssueShortLink (issueId: string): string {
|
||||||
|
@ -301,6 +301,9 @@ export default mergeIds(trackerId, tracker, {
|
|||||||
IssueTemplatePresenter: '' as AnyComponent
|
IssueTemplatePresenter: '' as AnyComponent
|
||||||
},
|
},
|
||||||
function: {
|
function: {
|
||||||
IssueTitleProvider: '' as Resource<(client: Client, ref: Ref<Doc>) => Promise<string>>
|
IssueTitleProvider: '' as Resource<(client: Client, ref: Ref<Doc>) => Promise<string>>,
|
||||||
|
GetIssueId: '' as Resource<(doc: Doc, props: Record<string, any>) => Promise<string>>,
|
||||||
|
GetIssueLink: '' as Resource<(doc: Doc, props: Record<string, any>) => Promise<string>>,
|
||||||
|
GetIssueTitle: '' as Resource<(doc: Doc, props: Record<string, any>) => Promise<string>>
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -18,6 +18,41 @@ import view from './plugin'
|
|||||||
import { FocusSelection, focusStore, previewDocument, SelectDirection, selectionStore } from './selection'
|
import { FocusSelection, focusStore, previewDocument, SelectDirection, selectionStore } from './selection'
|
||||||
import { deleteObject } from './utils'
|
import { deleteObject } from './utils'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Action to be used for copying text to clipboard.
|
||||||
|
* In Safari a request to write to the clipboard must be triggered during a user gesture.
|
||||||
|
* A call to clipboard.write or clipboard.writeText outside the scope of a user
|
||||||
|
* gesture(such as "click" or "touch" event handlers) will result in the immediate
|
||||||
|
* rejection of the promise returned by the API call.
|
||||||
|
* https://webkit.org/blog/10855/async-clipboard-api/
|
||||||
|
*
|
||||||
|
* * Require props:
|
||||||
|
* - textProvider - a function that provides text to be copied.
|
||||||
|
* - props - additional text provider props.
|
||||||
|
*/
|
||||||
|
async function CopyTextToClipboard (
|
||||||
|
doc: Doc,
|
||||||
|
evt: Event,
|
||||||
|
props: {
|
||||||
|
textProvider: Resource<(doc: Doc, props?: Record<string, any>) => Promise<string>>
|
||||||
|
props?: Record<string, any>
|
||||||
|
}
|
||||||
|
): Promise<void> {
|
||||||
|
const getText = await getResource(props.textProvider)
|
||||||
|
try {
|
||||||
|
// Safari specific behavior
|
||||||
|
// see https://bugs.webkit.org/show_bug.cgi?id=222262
|
||||||
|
const clipboardItem = new ClipboardItem({
|
||||||
|
'text/plain': getText(doc, props.props)
|
||||||
|
})
|
||||||
|
await navigator.clipboard.write([clipboardItem])
|
||||||
|
} catch {
|
||||||
|
// Fallback to default clipboard API implementation
|
||||||
|
const text = await getText(doc, props.props)
|
||||||
|
await navigator.clipboard.writeText(text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function Delete (object: Doc): void {
|
function Delete (object: Doc): void {
|
||||||
showPopup(
|
showPopup(
|
||||||
MessageBox,
|
MessageBox,
|
||||||
@ -334,6 +369,7 @@ async function getPopupAlignment (
|
|||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
export const actionImpl = {
|
export const actionImpl = {
|
||||||
|
CopyTextToClipboard,
|
||||||
Delete,
|
Delete,
|
||||||
Move,
|
Move,
|
||||||
MoveUp,
|
MoveUp,
|
||||||
|
@ -503,6 +503,10 @@ const view = plugin(viewId, {
|
|||||||
PositionElementAlignment: '' as Resource<(e?: Event) => PopupAlignment | undefined>
|
PositionElementAlignment: '' as Resource<(e?: Event) => PopupAlignment | undefined>
|
||||||
},
|
},
|
||||||
actionImpl: {
|
actionImpl: {
|
||||||
|
CopyTextToClipboard: '' as ViewAction<{
|
||||||
|
textProvider: Resource<(doc: Doc, props: Record<string, any>) => Promise<string>>
|
||||||
|
props?: Record<string, any>
|
||||||
|
}>,
|
||||||
UpdateDocument: '' as ViewAction<{
|
UpdateDocument: '' as ViewAction<{
|
||||||
key: string
|
key: string
|
||||||
value: any
|
value: any
|
||||||
|
Loading…
Reference in New Issue
Block a user