mirror of
https://github.com/hcengineering/platform.git
synced 2025-05-10 17:30:51 +00:00
272 lines
8.2 KiB
TypeScript
272 lines
8.2 KiB
TypeScript
// Copyright © 2025 Hardcore Engineering Inc.
|
|
//
|
|
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License. You may
|
|
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
//
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
import { type Card, CardEvents, cardId, type CardSpace, type CardSection, type MasterTag } from '@hcengineering/card'
|
|
import {
|
|
type Class,
|
|
type Client,
|
|
type Data,
|
|
type Doc,
|
|
type DocumentQuery,
|
|
fillDefaults,
|
|
type Hierarchy,
|
|
type MarkupBlobRef,
|
|
type Ref,
|
|
type RelatedDocument,
|
|
SortingOrder,
|
|
type Space,
|
|
type TxOperations,
|
|
type WithLookup
|
|
} from '@hcengineering/core'
|
|
import { getClient, MessageBox, type ObjectSearchResult } from '@hcengineering/presentation'
|
|
import {
|
|
getCurrentResolvedLocation,
|
|
getPanelURI,
|
|
type Location,
|
|
type ResolvedLocation,
|
|
showPopup
|
|
} from '@hcengineering/ui'
|
|
import view, { encodeObjectURI } from '@hcengineering/view'
|
|
import { accessDeniedStore } from '@hcengineering/view-resources'
|
|
import workbench, { type LocationData } from '@hcengineering/workbench'
|
|
import { translate } from '@hcengineering/platform'
|
|
import { makeRank } from '@hcengineering/rank'
|
|
import { Analytics } from '@hcengineering/analytics'
|
|
import { openWidget } from '@hcengineering/workbench-resources'
|
|
|
|
import CardSearchItem from './components/CardSearchItem.svelte'
|
|
import CreateSpace from './components/navigator/CreateSpace.svelte'
|
|
import card from './plugin'
|
|
import { type NavigatorConfig } from './types'
|
|
|
|
export async function deleteMasterTag (tag: MasterTag | undefined, onDelete?: () => void): Promise<void> {
|
|
if (tag !== undefined) {
|
|
const client = getClient()
|
|
if (tag._class === card.class.MasterTag) {
|
|
showPopup(MessageBox, {
|
|
label: card.string.DeleteMasterTag,
|
|
message: card.string.DeleteMasterTagConfirm,
|
|
action: async () => {
|
|
onDelete?.()
|
|
await client.update(tag, { removed: true })
|
|
}
|
|
})
|
|
} else {
|
|
showPopup(MessageBox, {
|
|
label: card.string.DeleteTag,
|
|
message: card.string.DeleteTagConfirm,
|
|
action: async () => {
|
|
onDelete?.()
|
|
await client.remove(tag)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
export async function resolveLocation (loc: Location): Promise<ResolvedLocation | undefined> {
|
|
if (loc.path[2] !== cardId) {
|
|
return undefined
|
|
}
|
|
|
|
const id = loc.path[3]
|
|
if (loc.path[4] === undefined && id !== undefined && id !== 'browser') {
|
|
return await generateLocation(loc, id)
|
|
}
|
|
}
|
|
|
|
export async function editSpace (value: CardSpace | undefined): Promise<void> {
|
|
if (value !== undefined) {
|
|
showPopup(CreateSpace, { space: value })
|
|
}
|
|
}
|
|
|
|
async function generateLocation (loc: Location, id: string): Promise<ResolvedLocation | undefined> {
|
|
const client = getClient()
|
|
const doc = await client.findOne(card.class.Card, { _id: id as Ref<Card> })
|
|
if (doc === undefined) {
|
|
accessDeniedStore.set(true)
|
|
return undefined
|
|
}
|
|
const appComponent = loc.path[0] ?? ''
|
|
const workspace = loc.path[1] ?? ''
|
|
const space = doc.space
|
|
const special = doc._class
|
|
|
|
const objectPanel = client.getHierarchy().classHierarchyMixin(doc._class as Ref<Class<Doc>>, view.mixin.ObjectPanel)
|
|
const component = objectPanel?.component ?? view.component.EditDoc
|
|
|
|
return {
|
|
loc: {
|
|
path: [appComponent, workspace],
|
|
fragment: getPanelURI(component, doc._id, doc._class, 'content')
|
|
},
|
|
defaultLocation: {
|
|
path: [appComponent, workspace, cardId, space, special],
|
|
fragment: getPanelURI(component, doc._id, doc._class, 'content')
|
|
}
|
|
}
|
|
}
|
|
|
|
export async function resolveLocationData (loc: Location): Promise<LocationData> {
|
|
const special = loc.path[3]
|
|
const base = { nameIntl: card.string.Cards }
|
|
if (special == null) {
|
|
return base
|
|
}
|
|
|
|
if (special === 'cards') {
|
|
return base
|
|
}
|
|
|
|
const client = getClient()
|
|
const object = await client.findOne(card.class.Card, { _id: special as Ref<Card> })
|
|
|
|
if (object === undefined) {
|
|
return base
|
|
}
|
|
|
|
return { name: object.title }
|
|
}
|
|
|
|
export async function getCardTitle (client: TxOperations, ref: Ref<Card>, doc?: Card): Promise<string> {
|
|
const object = doc ?? (await client.findOne(card.class.Card, { _id: ref }))
|
|
if (object === undefined) throw new Error(`Card not found, _id: ${ref}`)
|
|
return object.title
|
|
}
|
|
|
|
export async function getCardId (client: TxOperations, ref: Ref<Card>, doc?: Card): Promise<string> {
|
|
const object = doc ?? (await client.findOne(card.class.Card, { _id: ref }))
|
|
if (object === undefined) throw new Error(`Card not found, _id: ${ref}`)
|
|
return object.title
|
|
}
|
|
|
|
export async function getCardLink (doc: Card): Promise<Location> {
|
|
const loc = getCurrentResolvedLocation()
|
|
loc.path.length = 2
|
|
loc.fragment = undefined
|
|
loc.query = undefined
|
|
loc.path[2] = cardId
|
|
loc.path[3] = doc._id
|
|
|
|
return loc
|
|
}
|
|
|
|
export async function queryCard (
|
|
client: Client,
|
|
search: string,
|
|
filter?: { in?: RelatedDocument[], nin?: RelatedDocument[] }
|
|
): Promise<ObjectSearchResult[]> {
|
|
const q: DocumentQuery<Card> = { title: { $like: `%${search}%` } }
|
|
if (filter?.in !== undefined || filter?.nin !== undefined) {
|
|
q._id = {}
|
|
if (filter.in !== undefined) {
|
|
q._id.$in = filter.in?.map((it) => it._id as Ref<Card>)
|
|
}
|
|
if (filter.nin !== undefined) {
|
|
q._id.$nin = filter.nin?.map((it) => it._id as Ref<Card>)
|
|
}
|
|
}
|
|
return (await client.findAll(card.class.Card, q, { limit: 200 })).map(toCardObjectSearchResult)
|
|
}
|
|
|
|
const toCardObjectSearchResult = (e: WithLookup<Card>): ObjectSearchResult => ({
|
|
doc: e,
|
|
title: e.title,
|
|
icon: card.icon.Card,
|
|
component: CardSearchItem
|
|
})
|
|
|
|
export async function createCard (type: Ref<MasterTag>, space: Ref<Space>): Promise<Ref<Card>> {
|
|
const client = getClient()
|
|
const hierarchy = client.getHierarchy()
|
|
const lastOne = await client.findOne(card.class.Card, {}, { sort: { rank: SortingOrder.Descending } })
|
|
const title = await translate(card.string.Card, {})
|
|
|
|
const data: Data<Card> = {
|
|
title,
|
|
rank: makeRank(lastOne?.rank, undefined),
|
|
content: '' as MarkupBlobRef,
|
|
parentInfo: [],
|
|
blobs: {}
|
|
}
|
|
|
|
const filledData = fillDefaults(hierarchy, data, type)
|
|
|
|
const _id = await client.createDoc(type, space, filledData)
|
|
|
|
Analytics.handleEvent(CardEvents.CardCreated)
|
|
return _id
|
|
}
|
|
|
|
export function getRootType (hierarchy: Hierarchy, type: Ref<MasterTag>): Ref<MasterTag> {
|
|
const ancestors = hierarchy.getAncestors(type)
|
|
const idx = ancestors.indexOf(card.class.Card)
|
|
|
|
return idx > 0 ? ancestors[idx - 1] : type
|
|
}
|
|
|
|
export function sortNavigatorTypes (types: MasterTag[], config: NavigatorConfig): MasterTag[] {
|
|
return types.sort((a, b) => {
|
|
const aOrder = config.preorder?.find((it) => it.type === a._id)?.order ?? Infinity
|
|
const bOrder = config.preorder?.find((it) => it.type === b._id)?.order ?? Infinity
|
|
|
|
if (aOrder !== bOrder) {
|
|
return aOrder - bOrder
|
|
}
|
|
|
|
return a.label.localeCompare(b.label)
|
|
})
|
|
}
|
|
|
|
export interface CardWidgetData {
|
|
_id: Ref<Card>
|
|
name: string
|
|
tab?: Ref<CardSection>
|
|
}
|
|
|
|
export async function openCardInSidebar (cardId: Ref<Card>, doc?: Card, tabId?: Ref<CardSection>): Promise<void> {
|
|
const client = getClient()
|
|
|
|
const widget = client.getModel().findAllSync(workbench.class.Widget, { _id: card.ids.CardWidget as any })[0]
|
|
if (widget === undefined) return
|
|
|
|
const object = doc ?? (await client.findOne(card.class.Card, { _id: cardId }))
|
|
if (object === undefined) return
|
|
|
|
const data: CardWidgetData = {
|
|
_id: cardId,
|
|
name: object.title,
|
|
tab: tabId
|
|
}
|
|
|
|
openWidget(widget, data)
|
|
}
|
|
|
|
export function cardCustomLinkMatch (doc: Card): boolean {
|
|
const loc = getCurrentResolvedLocation()
|
|
const client = getClient()
|
|
const alias = loc.path[2]
|
|
const app = client.getModel().findAllSync(workbench.class.Application, {
|
|
alias
|
|
})[0]
|
|
|
|
return app.type === 'cards'
|
|
}
|
|
|
|
export function cardCustomLinkEncode (doc: Card): Location {
|
|
const loc = getCurrentResolvedLocation()
|
|
loc.path[3] = encodeObjectURI(doc._id, card.class.Card)
|
|
return loc
|
|
}
|