Allow to perform markdown copy (#10090)

This commit is contained in:
Andrey Sobolev
2025-10-14 22:28:26 +07:00
committed by GitHub
parent 57d2c5b666
commit 7b7aa1041d
21 changed files with 119 additions and 25 deletions
+8
View File
@@ -247,6 +247,14 @@
"safeForSimultaneousRushProcesses": true,
"shellCommand": "./common/scripts/docker.sh"
},
{
"commandKind": "global",
"name": "docker-server",
"summary": "Build docker with platform",
"description": "use to build all docker containers required for platform",
"safeForSimultaneousRushProcesses": true,
"shellCommand": "./common/scripts/docker-server.sh && rush docker:up"
},
{
"commandKind": "global",
"name": "docker:up",
+2 -1
View File
@@ -6277,7 +6277,7 @@ packages:
version: 0.0.0
'@rush-temp/view-resources@file:projects/view-resources.tgz':
resolution: {integrity: sha512-L3IzKi6Lo78o7lDzz+V2Ap+7lStsOm0mhIuaOtFk/UQQjSHp8CVcN0RwubG+KEHV/zv2li/pXTjNes3Ayg+Xsw==, tarball: file:projects/view-resources.tgz}
resolution: {integrity: sha512-iBRhD5GGptL67zFov48Awf3MSVsnV6uctkmKFRv7EzGXUXpbS5E5uv3mS0TUlwEV2GCGIxejsbnQ8tJrHYqMNw==, tarball: file:projects/view-resources.tgz}
version: 0.0.0
'@rush-temp/view@file:projects/view.tgz':
@@ -34768,6 +34768,7 @@ snapshots:
'@hcengineering/platform-rig': 0.7.19(@babel/core@7.23.9)(postcss-load-config@4.0.2(postcss@8.5.3)(ts-node@10.9.2(@swc/core@1.13.5)(@types/node@22.15.29)(typescript@5.9.3)))(postcss@8.5.3)(sass@1.93.2)(ts-node@10.9.2(@swc/core@1.13.5)(@types/node@22.15.29)(typescript@5.9.3))
'@hcengineering/query': 0.7.6
'@hcengineering/text': 0.7.5(prosemirror-inputrules@1.4.0)(prosemirror-model@1.24.1)(prosemirror-state@1.4.3)(prosemirror-view@1.37.2)
'@hcengineering/text-markdown': 0.7.5
'@types/jest': 29.5.12
'@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.9.3))(eslint@8.56.0)(typescript@5.9.3)
'@typescript-eslint/parser': 6.21.0(eslint@8.56.0)(typescript@5.9.3)
+3
View File
@@ -0,0 +1,3 @@
rush docker:build -p 20 \
--to @hcengineering/pod-server \
--to @hcengineering/tool
+18
View File
@@ -101,6 +101,24 @@ export function createActions (builder: Builder): void {
card.action.UnsetParent
)
createAction(builder, {
action: view.actionImpl.CopyDocumentMarkdown,
actionProps: {
contentClass: card.class.Card,
contentField: 'content'
},
label: view.string.CopyDocumentMarkdown,
icon: view.icon.Print,
input: 'any',
category: card.category.Card,
target: card.class.Card,
query: {},
context: {
mode: ['context', 'browser'],
group: 'tools'
}
})
createAction(builder, {
action: view.actionImpl.ShowPopup,
actionProps: {
@@ -458,6 +458,7 @@
onContentError: ({ error, disableCollaboration }) => {
disableCollaboration()
contentError = true
console.error(error)
Analytics.handleError(error)
}
})
+3
View File
@@ -275,4 +275,7 @@
<path d="M13 21.0005C13 20.4482 12.5523 20.0005 12 20.0005H7C6.44772 20.0005 6 20.4482 6 21.0005C6 21.5528 6.44772 22.0005 7 22.0005H12C12.5523 22.0005 13 21.5528 13 21.0005Z"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M26 4.00012C28.2091 4.00012 30 5.79098 30 8.00012V24.0001C30 26.2093 28.2091 28.0001 26 28.0001H6C3.79086 28.0001 2 26.2093 2 24.0001V8.00012C2 5.79098 3.79086 4.00012 6 4.00012H26ZM17 6.00012H26C27.1046 6.00012 28 6.89555 28 8.00012V24.0001C28 25.1047 27.1046 26.0001 26 26.0001H17V6.00012ZM15 6.00012V26.0001H6C4.89543 26.0001 4 25.1047 4 24.0001V8.00012C4 6.89555 4.89543 6.00012 6 6.00012H15Z"/>
</symbol>
<symbol id="print" viewBox="0 0 32 32">
<path fill-rule="evenodd" clip-rule="evenodd" d="M9 4C7.89543 4 7 4.89543 7 6V26C7 27.1046 7.89543 28 9 28H23C24.1046 28 25 27.1046 25 26V12H21C18.7909 12 17 10.2091 17 8V4H9ZM19 4.41421V8C19 9.10457 19.8954 10 21 10H24.5858L19 4.41421ZM5 6C5 3.79086 6.79086 2 9 2H18.5858C19.1162 2 19.6249 2.21071 20 2.58579L26.4142 9C26.7893 9.37507 27 9.88378 27 10.4142V26C27 28.2091 25.2091 30 23 30H9C6.79086 30 5 28.2091 5 26V6ZM10 17C10 16.4477 10.4477 16 11 16H21C21.5523 16 22 16.4477 22 17C22 17.5523 21.5523 18 21 18H11C10.4477 18 10 17.5523 10 17ZM10 23C10 22.4477 10.4477 22 11 22H21C21.5523 22 22 22.4477 22 23C22 23.5523 21.5523 24 21 24H11C10.4477 24 10 23.5523 10 23Z" />
</symbol>
</svg>

Before

Width:  |  Height:  |  Size: 61 KiB

After

Width:  |  Height:  |  Size: 62 KiB

+1
View File
@@ -112,6 +112,7 @@
"Or": "Nebo",
"HyperlinkPlaceholder": "https://jappleseed.com",
"CopyToClipboard": "Kopírovat do schránky",
"CopyDocumentMarkdown": "Kopírovat jako Markdown",
"PublicView": "Veřejné",
"ChooseIcon": "Vybrat ikonu",
+1
View File
@@ -112,6 +112,7 @@
"Or": "Oder",
"HyperlinkPlaceholder": "https://jappleseed.com",
"CopyToClipboard": "In die Zwischenablage kopieren",
"CopyDocumentMarkdown": "Als Markdown kopieren",
"PublicView": "Öffentlich",
"ChooseIcon": "Symbol wählen",
+1
View File
@@ -112,6 +112,7 @@
"Or": "Or",
"HyperlinkPlaceholder": "https://jappleseed.com",
"CopyToClipboard": "Copy to clipboard",
"CopyDocumentMarkdown": "Copy as Markdown",
"PublicView": "Public",
"ChooseIcon": "Choose icon",
+1
View File
@@ -108,6 +108,7 @@
"Or": "O",
"HyperlinkPlaceholder": "https://jappleseed.com",
"CopyToClipboard": "Copiar al portapapeles",
"CopyDocumentMarkdown": "Copiar como Markdown",
"PublicView": "Público",
"ChooseIcon": "Elegir icono",
"IconColor": "Elegir color del icono",
+1
View File
@@ -108,6 +108,7 @@
"Or": "Ou",
"HyperlinkPlaceholder": "https://jappleseed.com",
"CopyToClipboard": "Copier dans le presse-papiers",
"CopyDocumentMarkdown": "Copier en Markdown",
"PublicView": "Vue publique",
"ChooseIcon": "Choisir une icône",
"IconColor": "Choisir la couleur de l'icône",
+1
View File
@@ -108,6 +108,7 @@
"Or": "O",
"HyperlinkPlaceholder": "https://jappleseed.com",
"CopyToClipboard": "Copia negli appunti",
"CopyDocumentMarkdown": "Copia come Markdown",
"PublicView": "Vista pubblica",
"ChooseIcon": "Scegli icona",
"IconColor": "Scegli colore icona",
+1
View File
@@ -108,6 +108,7 @@
"Or": "または",
"HyperlinkPlaceholder": "https://jappleseed.com",
"CopyToClipboard": "クリップボードにコピー",
"CopyDocumentMarkdown": "Markdownとしてコピー",
"PublicView": "公開",
"ChooseIcon": "アイコンを選択",
"IconColor": "アイコンの色を選択",
+1
View File
@@ -108,6 +108,7 @@
"Or": "Ou",
"HyperlinkPlaceholder": "https://jappleseed.com",
"CopyToClipboard": "Copiar para área de transferência",
"CopyDocumentMarkdown": "Copiar como Markdown",
"PublicView": "Público",
"ChooseIcon": "Escolher ícone",
"IconColor": "Escolher cor do ícone",
+1
View File
@@ -108,6 +108,7 @@
"Or": "Или",
"HyperlinkPlaceholder": "https://jappleseed.com",
"CopyToClipboard": "Скопировать в буфер обмена",
"CopyDocumentMarkdown": "Скопировать как Markdown",
"PublicView": "Публичный",
"ChooseIcon": "Выбрать иконку",
+1
View File
@@ -108,6 +108,7 @@
"Or": "Veya",
"HyperlinkPlaceholder": "https://ornek.com",
"CopyToClipboard": "Panoya kopyala",
"CopyDocumentMarkdown": "Markdown olarak kopyala",
"PublicView": "Genel",
"ChooseIcon": "İkon seç",
"IconColor": "İkon rengi seç",
+1
View File
@@ -112,6 +112,7 @@
"Or": "或",
"HyperlinkPlaceholder": "https://jappleseed.com",
"CopyToClipboard": "复制到剪贴板",
"CopyDocumentMarkdown": "复制为 Markdown",
"PublicView": "公开视图",
"ChooseIcon": "选择图标",
+2 -1
View File
@@ -67,7 +67,8 @@ loadMetadata(view.icon, {
Feather: `${icons}#feather`,
MasterDetail: `${icons}#master-detail`,
Tree: `${icons}#tree`,
Document: `${icons}#document`
Document: `${icons}#document`,
Print: `${icons}#print`
})
loadMetadata(core.icon, {
TypeString: `${icons}#string`,
+1
View File
@@ -49,6 +49,7 @@
"@hcengineering/ui": "^0.7.0",
"@hcengineering/task": "^0.7.0",
"@hcengineering/text": "^0.7.5",
"@hcengineering/text-markdown": "^0.7.5",
"@hcengineering/preference": "^0.7.0",
"@hcengineering/notification": "^0.7.0",
"@hcengineering/presentation": "^0.7.0",
+60 -20
View File
@@ -1,4 +1,6 @@
import {
import contact from '@hcengineering/contact'
import core, {
type Blob,
type Class,
type Doc,
type DocumentQuery,
@@ -6,17 +8,21 @@ import {
type Ref,
type Space,
type TxResult,
getCurrentAccount
getCurrentAccount,
makeDocCollabId
} from '@hcengineering/core'
import { type Asset, type IntlString, type Resource, getResource } from '@hcengineering/platform'
import {
MessageBox,
getClient,
updateAttribute,
type ContextStore,
MessageBox,
contextStore,
copyTextToClipboardOldBrowser
copyTextToClipboardOldBrowser,
getClient,
getMarkup,
updateAttribute
} from '@hcengineering/presentation'
import { markupToJSON } from '@hcengineering/text'
import { markupToMarkdown } from '@hcengineering/text-markdown'
import {
type AnyComponent,
type AnySvelteComponent,
@@ -24,10 +30,12 @@ import {
type PopupPosAlignment,
closeTooltip,
isPopupPosAlignment,
locationToUrl,
navigate,
showPanel,
showPopup
} from '@hcengineering/ui'
import { get } from 'svelte/store'
import MoveView from './components/Move.svelte'
import view from './plugin'
import {
@@ -36,13 +44,10 @@ import {
type SelectionStore,
focusStore,
previewDocument,
selectionStore,
selectionLimit
selectionLimit,
selectionStore
} from './selection'
import { deleteObjects, getObjectId, getObjectLinkFragment, restrictionStore } from './utils'
import contact from '@hcengineering/contact'
import { locationToUrl } from '@hcengineering/ui'
import { get } from 'svelte/store'
/**
* Action to be used for copying text to clipboard.
@@ -65,21 +70,20 @@ async function CopyTextToClipboard (
}
): Promise<void> {
const getText = await getResource(props.textProvider)
const text = Array.isArray(doc)
? (await Promise.all(doc.map(async (d) => await getText(d, props.props)))).join(',')
: await getText(doc, props.props)
await copyText(text, 'text/plain')
}
async function copyText (text: any, contentType: string = 'text/plain'): Promise<void> {
try {
// Safari specific behavior
// see https://bugs.webkit.org/show_bug.cgi?id=222262
const text = Array.isArray(doc)
? (await Promise.all(doc.map(async (d) => await getText(d, props.props)))).join(',')
: await getText(doc, props.props)
const clipboardItem = new ClipboardItem({
'text/plain': text
})
await navigator.clipboard.write([clipboardItem])
} catch {
// Fallback to default clipboard API implementation
const text = Array.isArray(doc)
? (await Promise.all(doc.map(async (d) => await getText(d, props.props)))).join(',')
: await getText(doc, props.props)
if (navigator.clipboard != null && typeof navigator.clipboard.writeText === 'function') {
await navigator.clipboard.writeText(text)
} else copyTextToClipboardOldBrowser(text)
@@ -589,6 +593,41 @@ async function getPopupAlignment (
}
}
async function CopyDocumentMarkdown (
doc: Doc | Doc[],
evt: Event,
props: {
contentClass: Ref<Class<Doc>>
contentField: string
}
): Promise<void> {
const docs = Array.isArray(doc) ? doc : doc !== undefined ? [doc] : []
if (docs.length !== 1) {
return
}
const client = getClient()
const hierarchy = client.getHierarchy()
const contentClass = hierarchy.getClass(props.contentClass)
if (contentClass == null) {
return
}
const contentField = hierarchy.findAttribute(props.contentClass, props.contentField)
if (contentField == null) {
return
}
if (contentField.type._class === core.class.TypeCollaborativeDoc) {
const content = await getMarkup(
makeDocCollabId(docs[0], props.contentField),
(docs[0] as any)[props.contentField] as Ref<Blob>
)
if (content !== null) {
const jsonMarkup = markupToJSON(content)
const contentJson = markupToMarkdown(jsonMarkup)
await copyText(contentJson, 'text/markdown')
}
}
}
/**
* @public
*/
@@ -616,5 +655,6 @@ export const actionImpl = {
ShowPopup,
ShowEditor,
ValueSelector,
AttributeSelector
AttributeSelector,
CopyDocumentMarkdown
}
+10 -3
View File
@@ -156,7 +156,8 @@ const view = plugin(viewId, {
OpenInNewTab: '' as Ref<Action>,
RemoveRelation: '' as Ref<Action>,
CopyLink: '' as Ref<Action<Doc, any>>
CopyLink: '' as Ref<Action<Doc, any>>,
CopyDocumentMarkdown: '' as Ref<Action<Doc, any>>
},
viewlet: {
Table: '' as Ref<ViewletDescriptor>,
@@ -250,7 +251,8 @@ const view = plugin(viewId, {
Icon: '' as IntlString,
Select: '' as IntlString,
Color: '' as IntlString,
AutomationOnly: '' as IntlString
AutomationOnly: '' as IntlString,
CopyDocumentMarkdown: '' as IntlString
},
icon: {
Table: '' as Asset,
@@ -301,7 +303,8 @@ const view = plugin(viewId, {
Feather: '' as Asset,
MasterDetail: '' as Asset,
Tree: '' as Asset,
Document: '' as Asset
Document: '' as Asset,
Print: '' as Asset
},
category: {
General: '' as Ref<ActionCategory>,
@@ -344,6 +347,10 @@ const view = plugin(viewId, {
textProvider: Resource<(doc: Doc, props: Record<string, any>) => Promise<string>>
props?: Record<string, any>
}>,
CopyDocumentMarkdown: '' as ViewAction<{
contentClass: Ref<Class<Doc>>
contentField: string
}>,
UpdateDocument: '' as ViewAction<{
key: string
value: any