mirror of
https://github.com/hcengineering/platform.git
synced 2026-09-11 04:07:43 +02:00
Ability to duplicate a card (#10245)
Signed-off-by: Denis Bykhov <bykhov.denis@gmail.com>
This commit is contained in:
@@ -243,4 +243,22 @@ export function createActions (builder: Builder): void {
|
||||
group: 'edit'
|
||||
}
|
||||
})
|
||||
|
||||
createAction(
|
||||
builder,
|
||||
{
|
||||
action: card.actionImpl.DuplicateCard,
|
||||
label: card.string.Duplicate,
|
||||
icon: card.icon.Duplicate,
|
||||
input: 'focus',
|
||||
category: card.category.Card,
|
||||
target: card.class.Card,
|
||||
context: {
|
||||
mode: ['context', 'browser'],
|
||||
application: card.app.Card,
|
||||
group: 'associate'
|
||||
}
|
||||
},
|
||||
card.action.Duplicate
|
||||
)
|
||||
}
|
||||
|
||||
@@ -29,13 +29,15 @@ export default mergeIds(cardId, card, {
|
||||
},
|
||||
actionImpl: {
|
||||
DeleteMasterTag: '' as ViewAction,
|
||||
DuplicateCard: '' as ViewAction,
|
||||
EditSpace: '' as ViewAction
|
||||
},
|
||||
action: {
|
||||
DeleteMasterTag: '' as Ref<Action>,
|
||||
SetParent: '' as Ref<Action<Doc, any>>,
|
||||
UnsetParent: '' as Ref<Action<Doc, any>>,
|
||||
PublicLink: '' as Ref<Action<Doc, any>>
|
||||
PublicLink: '' as Ref<Action<Doc, any>>,
|
||||
Duplicate: '' as Ref<Action>
|
||||
},
|
||||
string: {
|
||||
CreateCardPersmissionDescription: '' as IntlString,
|
||||
|
||||
@@ -69,4 +69,8 @@
|
||||
<path d="M9.99998 18.7501C9.89651 18.75 9.79468 18.7243 9.70367 18.6751L2.12886 14.5963C1.82502 14.4327 1.71137 14.0537 1.87505 13.7499C2.03867 13.4462 2.41749 13.3326 2.72124 13.4962L9.99998 17.4152L17.2785 13.4963C17.5823 13.3327 17.9613 13.4464 18.125 13.7502C18.2886 14.0541 18.1749 14.4332 17.871 14.5968L10.2963 18.6755C10.2052 18.7246 10.1034 18.7502 9.99998 18.7501Z" fill="currentColor"/>
|
||||
<path d="M9.99998 11.2501C9.89651 11.25 9.79468 11.2243 9.70367 11.1751L1.57867 6.80005C1.47934 6.74654 1.39635 6.66713 1.33851 6.57026C1.28066 6.47338 1.25012 6.36266 1.25012 6.24983C1.25012 6.13701 1.28066 6.02628 1.33851 5.92941C1.39635 5.83254 1.47934 5.75313 1.57867 5.69961L9.70367 1.32461C9.7947 1.27548 9.89653 1.24976 9.99998 1.24976C10.1034 1.24976 10.2053 1.27548 10.2963 1.32461L18.4213 5.69961C18.5206 5.75313 18.6036 5.83254 18.6615 5.92941C18.7193 6.02628 18.7498 6.13701 18.7498 6.24983C18.7498 6.36266 18.7193 6.47338 18.6615 6.57026C18.6036 6.66713 18.5206 6.74654 18.4213 6.80005L10.2963 11.1751C10.2053 11.2243 10.1034 11.25 9.99998 11.2501ZM3.19336 6.25005L9.99998 9.91524L16.8066 6.25005L9.99998 2.58493L3.19336 6.25005Z" fill="currentColor"/>
|
||||
</symbol>
|
||||
<symbol id="duplicate" viewBox="0 0 16 16">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M5 3C5 1.89543 5.89543 1 7 1H12C13.1046 1 14 1.89543 14 3V10C14 11.1046 13.1046 12 12 12H7C5.89543 12 5 11.1046 5 10V3ZM7 2C6.44772 2 6 2.44772 6 3V10C6 10.5523 6.44772 11 7 11H12C12.5523 11 13 10.5523 13 10V3C13 2.44772 12.5523 2 12 2H7Z" />
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M4 5C3.44772 5 3 5.44772 3 6V13C3 13.5523 3.44772 14 4 14H9C9.55228 14 10 13.5523 10 13V12H11V13C11 14.1046 10.1046 15 9 15H4C2.89543 15 2 14.1046 2 13V6C2 4.89543 2.89543 4 4 4H5V5H4Z" />
|
||||
</symbol>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 9.9 KiB After Width: | Height: | Size: 10 KiB |
@@ -59,6 +59,7 @@
|
||||
"ForbidUpdateCard": "Zakázat aktualizaci karty",
|
||||
"ForbidCreateCardPermission": "Zakázat vytváření karet",
|
||||
"ForbidAddTagPermission": "Zakázat přidávání štítků",
|
||||
"ForbidRemoveTag": "Zakázat odebírání štítků"
|
||||
"ForbidRemoveTag": "Zakázat odebírání štítků",
|
||||
"Duplicate": "Duplikovat"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,6 +59,7 @@
|
||||
"ForbidUpdateCard": "Aktualisieren der Karte verbieten",
|
||||
"ForbidCreateCardPermission": "Erstellen von Karten verbieten",
|
||||
"ForbidAddTagPermission": "Hinzufügen von Tags verbieten",
|
||||
"ForbidRemoveTag": "Entfernen von Tags verbieten"
|
||||
"ForbidRemoveTag": "Entfernen von Tags verbieten",
|
||||
"Duplicate": "Duplizieren"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,6 +59,7 @@
|
||||
"ForbidUpdateCard": "Forbid Update Card",
|
||||
"ForbidCreateCardPermission": "Forbid Create Card",
|
||||
"ForbidAddTagPermission": "Forbid Add Tag",
|
||||
"ForbidRemoveTag": "Forbid Remove Tag"
|
||||
"ForbidRemoveTag": "Forbid Remove Tag",
|
||||
"Duplicate": "Duplicate"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,6 +59,7 @@
|
||||
"ForbidUpdateCard": "Prohibir actualizar tarjeta",
|
||||
"ForbidCreateCardPermission": "Prohibir crear tarjeta",
|
||||
"ForbidAddTagPermission": "Prohibir añadir etiqueta",
|
||||
"ForbidRemoveTag": "Prohibir eliminar etiqueta"
|
||||
"ForbidRemoveTag": "Prohibir eliminar etiqueta",
|
||||
"Duplicate": "Duplicar"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,6 +59,7 @@
|
||||
"ForbidUpdateCard": "Interdire la mise à jour de la fiche",
|
||||
"ForbidCreateCardPermission": "Interdire la création de fiche",
|
||||
"ForbidAddTagPermission": "Interdire l'ajout d'étiquette",
|
||||
"ForbidRemoveTag": "Interdire la suppression d'étiquette"
|
||||
"ForbidRemoveTag": "Interdire la suppression d'étiquette",
|
||||
"Duplicate": "Dupliquer"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,6 +59,7 @@
|
||||
"ForbidUpdateCard": "Vieta aggiornamento scheda",
|
||||
"ForbidCreateCardPermission": "Vieta creazione scheda",
|
||||
"ForbidAddTagPermission": "Vieta aggiunta tag",
|
||||
"ForbidRemoveTag": "Vieta rimozione tag"
|
||||
"ForbidRemoveTag": "Vieta rimozione tag",
|
||||
"Duplicate": "Duplicato"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,6 +59,7 @@
|
||||
"ForbidUpdateCard": "カード更新を禁止",
|
||||
"ForbidCreateCardPermission": "カード作成を禁止",
|
||||
"ForbidAddTagPermission": "タグ追加を禁止",
|
||||
"ForbidRemoveTag": "タグ削除を禁止"
|
||||
"ForbidRemoveTag": "タグ削除を禁止",
|
||||
"Duplicate": "複製"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,6 +59,7 @@
|
||||
"ForbidUpdateCard": "Proibir atualização de cartão",
|
||||
"ForbidCreateCardPermission": "Proibir criação de cartão",
|
||||
"ForbidAddTagPermission": "Proibir adicionar etiqueta",
|
||||
"ForbidRemoveTag": "Proibir remover etiqueta"
|
||||
"ForbidRemoveTag": "Proibir remover etiqueta",
|
||||
"Duplicate": "Duplicar"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,6 +59,7 @@
|
||||
"ForbidUpdateCard": "Запретить обновление карточки",
|
||||
"ForbidCreateCardPermission": "Запретить создание карточки",
|
||||
"ForbidAddTagPermission": "Запретить добавление тега",
|
||||
"ForbidRemoveTag": "Запретить удаление тега"
|
||||
"ForbidRemoveTag": "Запретить удаление тега",
|
||||
"Duplicate": "Дублировать"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,6 +59,7 @@
|
||||
"ForbidUpdateCard": "Kart güncellemeyi yasakla",
|
||||
"ForbidCreateCardPermission": "Kart oluşturmayı yasakla",
|
||||
"ForbidAddTagPermission": "Etiket eklemeyi yasakla",
|
||||
"ForbidRemoveTag": "Etiket kaldırmayı yasakla"
|
||||
"ForbidRemoveTag": "Etiket kaldırmayı yasakla",
|
||||
"Duplicate": "Çoğalt"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,6 +59,7 @@
|
||||
"ForbidUpdateCard": "禁止更新卡片",
|
||||
"ForbidCreateCardPermission": "禁止创建卡片",
|
||||
"ForbidAddTagPermission": "禁止添加标签",
|
||||
"ForbidRemoveTag": "禁止移除标签"
|
||||
"ForbidRemoveTag": "禁止移除标签",
|
||||
"Duplicate": "复制"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,5 +28,6 @@ loadMetadata(card.icon, {
|
||||
Space: `${icons}#space`,
|
||||
Expand: `${icons}#expand`,
|
||||
Feed: `${icons}#feed`,
|
||||
All: `${icons}#all`
|
||||
All: `${icons}#all`,
|
||||
Duplicate: `${icons}#duplicate`
|
||||
})
|
||||
|
||||
@@ -33,7 +33,8 @@ import {
|
||||
checkRelationsSectionVisibility,
|
||||
getSpaceAccessPublicLink,
|
||||
canGetSpaceAccessPublicLink,
|
||||
cardFactory
|
||||
cardFactory,
|
||||
duplicateCard
|
||||
} from './utils'
|
||||
import ManageMasterTagsContent from './components/settings/ManageMasterTagsContent.svelte'
|
||||
import ManageMasterTagsTools from './components/settings/ManageMasterTagsTools.svelte'
|
||||
@@ -144,6 +145,7 @@ export default async (): Promise<Resources> => ({
|
||||
},
|
||||
actionImpl: {
|
||||
DeleteMasterTag: deleteMasterTag,
|
||||
DuplicateCard: duplicateCard,
|
||||
EditSpace: editSpace
|
||||
},
|
||||
function: {
|
||||
|
||||
@@ -125,6 +125,7 @@ export default mergeIds(cardId, card, {
|
||||
CardTitle: '' as IntlString,
|
||||
CardContent: '' as IntlString,
|
||||
Post: '' as IntlString,
|
||||
ShowLess: '' as IntlString
|
||||
ShowLess: '' as IntlString,
|
||||
Duplicate: '' as IntlString
|
||||
}
|
||||
})
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { type AccountClient, getClient as getAccountClientRaw } from '@hcengineering/account-client'
|
||||
import { Analytics } from '@hcengineering/analytics'
|
||||
import { type Card, CardEvents, cardId, type CardSpace, type MasterTag } from '@hcengineering/card'
|
||||
import core, {
|
||||
AccountRole,
|
||||
@@ -25,6 +27,7 @@ import core, {
|
||||
hasAccountRole,
|
||||
type Hierarchy,
|
||||
makeCollabId,
|
||||
makeDocCollabId,
|
||||
type Markup,
|
||||
type MarkupBlobRef,
|
||||
type Ref,
|
||||
@@ -35,14 +38,17 @@ import core, {
|
||||
type WithLookup
|
||||
} from '@hcengineering/core'
|
||||
import login from '@hcengineering/login'
|
||||
import { getMetadata, translate } from '@hcengineering/platform'
|
||||
import presentation, {
|
||||
createMarkup,
|
||||
getClient,
|
||||
getMarkup,
|
||||
IconWithEmoji,
|
||||
MessageBox,
|
||||
type ObjectSearchResult
|
||||
} from '@hcengineering/presentation'
|
||||
import { type AccountClient, getClient as getAccountClientRaw } from '@hcengineering/account-client'
|
||||
import { makeRank } from '@hcengineering/rank'
|
||||
import { EmptyMarkup, isEmptyMarkup } from '@hcengineering/text'
|
||||
import {
|
||||
getCurrentLocation,
|
||||
getCurrentResolvedLocation,
|
||||
@@ -50,18 +56,16 @@ import {
|
||||
type IconComponent,
|
||||
type IconProps,
|
||||
type Location,
|
||||
navigate,
|
||||
type ResolvedLocation,
|
||||
showPopup
|
||||
} from '@hcengineering/ui'
|
||||
import view, { encodeObjectURI, canCopyLink } from '@hcengineering/view'
|
||||
import view, { canCopyLink, encodeObjectURI } from '@hcengineering/view'
|
||||
import { accessDeniedStore } from '@hcengineering/view-resources'
|
||||
import workbench, { type LocationData, type Widget, type WidgetTab } from '@hcengineering/workbench'
|
||||
import { translate, getMetadata } from '@hcengineering/platform'
|
||||
import { makeRank } from '@hcengineering/rank'
|
||||
import { Analytics } from '@hcengineering/analytics'
|
||||
import { createWidgetTab } from '@hcengineering/workbench-resources'
|
||||
import { EmptyMarkup, isEmptyMarkup } from '@hcengineering/text'
|
||||
|
||||
import attachment from '@hcengineering/attachment'
|
||||
import CardSearchItem from './components/CardSearchItem.svelte'
|
||||
import CreateSpace from './components/navigator/CreateSpace.svelte'
|
||||
import card from './plugin'
|
||||
@@ -92,6 +96,82 @@ export async function deleteMasterTag (tag: MasterTag | undefined, onDelete?: ()
|
||||
}
|
||||
}
|
||||
|
||||
export async function duplicateCard (origin: Card): Promise<void> {
|
||||
const client = getClient()
|
||||
const h = client.getHierarchy()
|
||||
const props: Partial<Data<Card>> = {}
|
||||
const base = h.getBaseClass(origin._class)
|
||||
const mixins = h.findAllMixins(origin)
|
||||
const attrs = h.getAllAttributes(base, core.class.Doc)
|
||||
|
||||
for (const [key, attr] of attrs) {
|
||||
if (attr.readonly !== true && attr.hidden !== true) {
|
||||
if (attr.type._class === core.class.Collection) {
|
||||
;(props as any)[key] = 0
|
||||
} else if (
|
||||
attr.type._class !== core.class.TypeCollaborativeDoc &&
|
||||
attr.type._class !== core.class.TypeIdentifier
|
||||
) {
|
||||
;(props as any)[key] = (origin as any)[key]
|
||||
}
|
||||
}
|
||||
}
|
||||
props.title = `${origin.title} (Copy)`
|
||||
const targetId = generateId()
|
||||
const relationsA = await client.findAll(core.class.Relation, { docA: origin._id })
|
||||
const relationsB = await client.findAll(core.class.Relation, { docB: origin._id })
|
||||
|
||||
const markup = await getMarkup(makeDocCollabId(origin, 'content'), origin.content)
|
||||
if (!isEmptyMarkup(markup)) {
|
||||
const collabId = makeCollabId(base, targetId, 'content')
|
||||
props.content = await createMarkup(collabId, markup)
|
||||
}
|
||||
|
||||
const ops = client.apply(`Duplicate_card_${origin._id}`)
|
||||
await ops.createDoc(base, origin.space, props, targetId)
|
||||
for (const mixin of mixins) {
|
||||
const mixinAttrs = h.getOwnAttributes(mixin)
|
||||
const as = h.as(origin, mixin)
|
||||
const attributes: Partial<Data<Doc>> = {}
|
||||
for (const [key, attr] of mixinAttrs) {
|
||||
if (attr.readonly !== true && attr.hidden !== true) {
|
||||
;(attributes as any)[key] = (as as any)[key]
|
||||
}
|
||||
}
|
||||
await ops.createMixin(targetId, base, origin.space, mixin, attributes)
|
||||
}
|
||||
|
||||
for (const rel of relationsA) {
|
||||
await ops.createDoc(core.class.Relation, origin.space, {
|
||||
docA: targetId,
|
||||
docB: rel.docB,
|
||||
association: rel.association
|
||||
})
|
||||
}
|
||||
for (const rel of relationsB) {
|
||||
await ops.createDoc(core.class.Relation, origin.space, {
|
||||
docA: rel.docA,
|
||||
docB: targetId,
|
||||
association: rel.association
|
||||
})
|
||||
}
|
||||
await ops.commit()
|
||||
|
||||
const attachments = await client.findAll(attachment.class.Attachment, { attachedTo: origin._id })
|
||||
const attachmentOps = client.apply(`Duplicate_attachments_${origin._id}`)
|
||||
for (const att of attachments) {
|
||||
const { _id, modifiedBy, modifiedOn, attachedTo, attachedToClass, collection, space, ...props } = att
|
||||
await attachmentOps.addCollection(attachment.class.Attachment, origin.space, targetId, base, 'attachments', props)
|
||||
}
|
||||
await attachmentOps.commit()
|
||||
|
||||
const loc = getCurrentLocation()
|
||||
loc.path[2] = cardId
|
||||
loc.path[3] = targetId
|
||||
loc.path.length = 4
|
||||
navigate(loc)
|
||||
}
|
||||
|
||||
export async function resolveLocation (loc: Location): Promise<ResolvedLocation | undefined> {
|
||||
if (loc.path[2] !== cardId) {
|
||||
return undefined
|
||||
|
||||
@@ -169,7 +169,8 @@ const cardPlugin = plugin(cardId, {
|
||||
Space: '' as Asset,
|
||||
Expand: '' as Asset,
|
||||
Feed: '' as Asset,
|
||||
All: '' as Asset
|
||||
All: '' as Asset,
|
||||
Duplicate: '' as Asset
|
||||
},
|
||||
extensions: {
|
||||
EditCardExtension: '' as ComponentExtensionId
|
||||
|
||||
Reference in New Issue
Block a user