mirror of
https://github.com/hcengineering/platform.git
synced 2026-09-26 19:44:57 +02:00
Merge branch 'develop' of https://github.com/hcengineering/platform into staging-new
Signed-off-by: Artem Savchenko <armisav@gmail.com>
This commit is contained in:
Generated
+49
-1
@@ -6857,6 +6857,9 @@ importers:
|
||||
'@hcengineering/model-guest':
|
||||
specifier: workspace:^0.7.0
|
||||
version: link:../guest
|
||||
'@hcengineering/model-notification':
|
||||
specifier: workspace:^0.7.0
|
||||
version: link:../notification
|
||||
'@hcengineering/model-preference':
|
||||
specifier: workspace:^0.7.0
|
||||
version: link:../preference
|
||||
@@ -6872,6 +6875,9 @@ importers:
|
||||
'@hcengineering/model-workbench':
|
||||
specifier: workspace:^0.7.0
|
||||
version: link:../workbench
|
||||
'@hcengineering/notification':
|
||||
specifier: workspace:^0.7.0
|
||||
version: link:../../plugins/notification
|
||||
'@hcengineering/platform':
|
||||
specifier: workspace:^0.7.19
|
||||
version: link:../../foundations/core/packages/platform
|
||||
@@ -10425,6 +10431,9 @@ importers:
|
||||
'@hcengineering/server-core':
|
||||
specifier: workspace:^0.7.18
|
||||
version: link:../../foundations/server/packages/core
|
||||
'@hcengineering/server-notification':
|
||||
specifier: workspace:^0.7.0
|
||||
version: link:../../server-plugins/notification
|
||||
devDependencies:
|
||||
'@hcengineering/platform-rig':
|
||||
specifier: workspace:^0.7.19
|
||||
@@ -19008,6 +19017,12 @@ importers:
|
||||
'@hcengineering/platform':
|
||||
specifier: workspace:^0.7.19
|
||||
version: link:../../foundations/core/packages/platform
|
||||
'@hcengineering/presentation':
|
||||
specifier: workspace:^0.7.0
|
||||
version: link:../../packages/presentation
|
||||
'@hcengineering/ui':
|
||||
specifier: workspace:^0.7.0
|
||||
version: link:../../packages/ui
|
||||
'@hcengineering/view':
|
||||
specifier: workspace:^0.7.0
|
||||
version: link:../view
|
||||
@@ -19081,6 +19096,9 @@ importers:
|
||||
'@hcengineering/view-resources':
|
||||
specifier: workspace:^0.7.0
|
||||
version: link:../view-resources
|
||||
svelte:
|
||||
specifier: ^4.2.20
|
||||
version: 4.2.20
|
||||
devDependencies:
|
||||
'@hcengineering/platform-rig':
|
||||
specifier: workspace:^0.7.19
|
||||
@@ -19109,15 +19127,36 @@ importers:
|
||||
eslint-plugin-promise:
|
||||
specifier: ^6.1.1
|
||||
version: 6.6.0(eslint@8.57.1)
|
||||
eslint-plugin-svelte:
|
||||
specifier: ^2.35.1
|
||||
version: 2.46.1(eslint@8.57.1)(svelte@4.2.20)(ts-node@10.9.2(@swc/core@1.15.1)(@types/node@22.19.0)(typescript@5.9.3))
|
||||
jest:
|
||||
specifier: ^29.7.0
|
||||
version: 29.7.0(@types/node@22.19.0)(ts-node@10.9.2(@swc/core@1.15.1)(@types/node@22.19.0)(typescript@5.9.3))
|
||||
prettier:
|
||||
specifier: ^3.6.2
|
||||
version: 3.6.2
|
||||
prettier-plugin-svelte:
|
||||
specifier: ^3.4.0
|
||||
version: 3.4.0(prettier@3.6.2)(svelte@4.2.20)
|
||||
sass:
|
||||
specifier: ^1.80.0
|
||||
version: 1.93.3
|
||||
svelte-check:
|
||||
specifier: ^3.6.9
|
||||
version: 3.8.6(@babel/core@7.28.5)(postcss-load-config@4.0.2(postcss@8.5.6)(ts-node@10.9.2(@swc/core@1.15.1)(@types/node@22.19.0)(typescript@5.9.3)))(postcss@8.5.6)(sass@1.93.3)(svelte@4.2.20)
|
||||
svelte-eslint-parser:
|
||||
specifier: ^0.33.1
|
||||
version: 0.33.1(svelte@4.2.20)
|
||||
svelte-loader:
|
||||
specifier: ^3.2.0
|
||||
version: 3.2.4(svelte@4.2.20)
|
||||
svelte-preprocess:
|
||||
specifier: ^5.1.4
|
||||
version: 5.1.4(@babel/core@7.28.5)(postcss-load-config@4.0.2(postcss@8.5.6)(ts-node@10.9.2(@swc/core@1.15.1)(@types/node@22.19.0)(typescript@5.9.3)))(postcss@8.5.6)(sass@1.93.3)(svelte@4.2.20)(typescript@5.9.3)
|
||||
ts-jest:
|
||||
specifier: ^29.1.1
|
||||
version: 29.4.5(@babel/core@7.28.5)(@jest/transform@29.7.0)(@jest/types@30.2.0)(babel-jest@29.7.0(@babel/core@7.28.5))(jest-util@30.2.0)(jest@29.7.0)(typescript@5.9.3)
|
||||
version: 29.4.5(@babel/core@7.28.5)(@jest/transform@29.7.0)(@jest/types@30.2.0)(babel-jest@29.7.0(@babel/core@7.28.5))(esbuild@0.25.12)(jest-util@30.2.0)(jest@29.7.0(@types/node@22.19.0)(ts-node@10.9.2(@swc/core@1.15.1)(@types/node@22.19.0)(typescript@5.9.3)))(typescript@5.9.3)
|
||||
typescript:
|
||||
specifier: ^5.9.3
|
||||
version: 5.9.3
|
||||
@@ -32222,6 +32261,9 @@ importers:
|
||||
'@hcengineering/server-core':
|
||||
specifier: workspace:^0.7.18
|
||||
version: link:../../foundations/server/packages/core
|
||||
'@hcengineering/server-notification':
|
||||
specifier: workspace:^0.7.0
|
||||
version: link:../notification
|
||||
devDependencies:
|
||||
'@hcengineering/platform-rig':
|
||||
specifier: workspace:^0.7.19
|
||||
@@ -36536,6 +36578,12 @@ importers:
|
||||
'@hcengineering/drive-assets':
|
||||
specifier: workspace:^0.7.0
|
||||
version: link:../../plugins/drive-assets
|
||||
'@hcengineering/export':
|
||||
specifier: workspace:^0.7.0
|
||||
version: link:../../plugins/export
|
||||
'@hcengineering/export-assets':
|
||||
specifier: workspace:^0.7.0
|
||||
version: link:../../plugins/export-assets
|
||||
'@hcengineering/github':
|
||||
specifier: workspace:^0.7.0
|
||||
version: link:../../services/github/github
|
||||
|
||||
@@ -40,6 +40,8 @@
|
||||
"@hcengineering/converter": "workspace:^0.7.0",
|
||||
"@hcengineering/core": "workspace:^0.7.24",
|
||||
"@hcengineering/model": "workspace:^0.7.17",
|
||||
"@hcengineering/notification": "workspace:^0.7.0",
|
||||
"@hcengineering/model-notification": "workspace:^0.7.0",
|
||||
"@hcengineering/chunter": "workspace:^0.7.0",
|
||||
"@hcengineering/model-setting": "workspace:^0.7.0",
|
||||
"@hcengineering/model-view": "workspace:^0.7.0",
|
||||
|
||||
@@ -84,6 +84,7 @@ import { type BuildModelKey } from '@hcengineering/view'
|
||||
import { createActions } from './actions'
|
||||
import { definePermissions } from './permissions'
|
||||
import card from './plugin'
|
||||
import notification from '@hcengineering/notification'
|
||||
|
||||
export { cardId } from '@hcengineering/card'
|
||||
|
||||
@@ -149,6 +150,9 @@ export class TCard extends TDoc implements Card {
|
||||
@Hidden()
|
||||
@ReadOnly()
|
||||
peerId?: string
|
||||
|
||||
@Prop(Collection(chunter.class.ChatMessage), chunter.string.Comments)
|
||||
comments?: number
|
||||
}
|
||||
|
||||
@Model(card.class.CardSpace, core.class.TypedSpace, DOMAIN_SPACE)
|
||||
@@ -445,6 +449,47 @@ export function createModel (builder: Builder): void {
|
||||
PaletteColorIndexes.Arctic
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
notification.class.NotificationGroup,
|
||||
core.space.Model,
|
||||
{
|
||||
label: card.string.Card,
|
||||
icon: card.icon.Card
|
||||
},
|
||||
card.ids.CardNotificationGroup
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
notification.class.NotificationType,
|
||||
core.space.Model,
|
||||
{
|
||||
hidden: false,
|
||||
generated: false,
|
||||
label: card.string.CardUpdated,
|
||||
group: card.ids.CardNotificationGroup,
|
||||
txClasses: [core.class.TxUpdateDoc, core.class.TxMixin],
|
||||
objectClass: card.class.Card,
|
||||
defaultEnabled: false
|
||||
},
|
||||
card.ids.CardNotification
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
notification.class.NotificationType,
|
||||
core.space.Model,
|
||||
{
|
||||
hidden: false,
|
||||
generated: false,
|
||||
label: chunter.string.Comments,
|
||||
group: card.ids.CardNotificationGroup,
|
||||
txClasses: [core.class.TxCreateDoc],
|
||||
objectClass: chunter.class.ChatMessage,
|
||||
attachedToClass: card.class.Card,
|
||||
defaultEnabled: true
|
||||
},
|
||||
card.ids.CardMessageNotification
|
||||
)
|
||||
|
||||
builder.createDoc(view.class.Viewlet, core.space.Model, {
|
||||
attachTo: card.class.CardSpace,
|
||||
descriptor: view.viewlet.Table,
|
||||
@@ -683,6 +728,60 @@ export function createModel (builder: Builder): void {
|
||||
card.viewlet.CardRelationshipTable
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
presentation.class.ComponentPointExtension,
|
||||
core.space.Model,
|
||||
{
|
||||
extension: converter.extensions.CopyAsMarkdownAction,
|
||||
component: converter.component.CopyAsMarkdownButton
|
||||
},
|
||||
converter.extensions.CopyAsMarkdownButton
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
view.class.ViewletViewAction,
|
||||
core.space.Model,
|
||||
{
|
||||
descriptor: view.viewlet.RelationshipTable,
|
||||
extension: converter.extensions.CopyAsMarkdownAction,
|
||||
applicableToClass: card.class.Card
|
||||
},
|
||||
card.specialViewAction.CardRelationshipTable
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
view.class.ViewletViewAction,
|
||||
core.space.Model,
|
||||
{
|
||||
descriptor: view.viewlet.Table,
|
||||
extension: converter.extensions.CopyAsMarkdownAction,
|
||||
applicableToClass: card.class.Card
|
||||
},
|
||||
card.specialViewAction.CardTable
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
view.class.ViewletViewAction,
|
||||
core.space.Model,
|
||||
{
|
||||
descriptor: view.viewlet.Table,
|
||||
extension: converter.extensions.CopyAsMarkdownAction,
|
||||
applicableToClass: card.class.Card
|
||||
},
|
||||
card.specialViewAction.CopyAsMarkdownTable
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
view.class.ViewletViewAction,
|
||||
core.space.Model,
|
||||
{
|
||||
descriptor: view.viewlet.RelationshipTable,
|
||||
extension: converter.extensions.CopyAsMarkdownAction,
|
||||
applicableToClass: card.class.Card
|
||||
},
|
||||
card.specialViewAction.CopyAsMarkdownRelationshipTable
|
||||
)
|
||||
|
||||
builder.mixin(card.class.Card, core.class.Class, view.mixin.ObjectPresenter, {
|
||||
presenter: card.component.CardPresenter
|
||||
})
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import type { ViewletViewAction, Action, ActionCategory, ViewAction } from '@hcengineering/view'
|
||||
import { type Card, cardId } from '@hcengineering/card'
|
||||
import card from '@hcengineering/card-resources/src/plugin'
|
||||
import type { Client, Doc, Ref } from '@hcengineering/core'
|
||||
@@ -20,8 +21,8 @@ import {} from '@hcengineering/core'
|
||||
import { mergeIds, type Resource } from '@hcengineering/platform'
|
||||
import { type TagCategory } from '@hcengineering/tags'
|
||||
import { type Location, type ResolvedLocation } from '@hcengineering/ui/src/types'
|
||||
import { type Action, type ActionCategory, type ViewAction } from '@hcengineering/view'
|
||||
import { type LocationData } from '@hcengineering/workbench'
|
||||
import { type NotificationGroup, type NotificationType } from '@hcengineering/notification'
|
||||
|
||||
export default mergeIds(cardId, card, {
|
||||
app: {
|
||||
@@ -43,10 +44,19 @@ export default mergeIds(cardId, card, {
|
||||
Card: '' as Ref<ActionCategory>,
|
||||
Labels: '' as Ref<TagCategory>
|
||||
},
|
||||
specialViewAction: {
|
||||
CardRelationshipTable: '' as Ref<ViewletViewAction>,
|
||||
CardTable: '' as Ref<ViewletViewAction>,
|
||||
CopyAsMarkdownTable: '' as Ref<ViewletViewAction>,
|
||||
CopyAsMarkdownRelationshipTable: '' as Ref<ViewletViewAction>
|
||||
},
|
||||
ids: {
|
||||
MasterTags: '' as Ref<Doc>,
|
||||
ManageMasterTags: '' as Ref<Doc>,
|
||||
TagRelations: '' as Ref<Doc>
|
||||
TagRelations: '' as Ref<Doc>,
|
||||
CardNotificationGroup: '' as Ref<NotificationGroup>,
|
||||
CardNotification: '' as Ref<NotificationType>,
|
||||
CardMessageNotification: '' as Ref<NotificationType>
|
||||
},
|
||||
resolver: {
|
||||
Location: '' as Resource<(loc: Location) => Promise<ResolvedLocation | undefined>>,
|
||||
|
||||
@@ -21,6 +21,7 @@ import documentsPlugin, {
|
||||
DocumentState,
|
||||
type Document,
|
||||
type DocumentSpace,
|
||||
type DocumentTemplate,
|
||||
type ProjectDocument,
|
||||
type ChangeControl,
|
||||
type DocumentRequest
|
||||
@@ -43,7 +44,7 @@ import setting from '@hcengineering/setting'
|
||||
import tags from '@hcengineering/tags'
|
||||
import textEditor from '@hcengineering/text-editor'
|
||||
|
||||
import { AccountRole, type ClassCollaborators, type Class, type Doc, type Ref } from '@hcengineering/core'
|
||||
import { AccountRole, type ClassCollaborators, type Class, type Doc, type Lookup, type Ref } from '@hcengineering/core'
|
||||
import { type Action } from '@hcengineering/view'
|
||||
import { definePermissions } from './permissions'
|
||||
import documents from './plugin'
|
||||
@@ -255,6 +256,11 @@ export function createModel (builder: Builder): void {
|
||||
)
|
||||
|
||||
// Workflow
|
||||
const documentTableLookup: Lookup<Document> = {
|
||||
owner: contact.mixin.Employee,
|
||||
category: documents.class.DocumentCategory,
|
||||
template: documents.mixin.DocumentTemplate
|
||||
}
|
||||
builder.createDoc(
|
||||
view.class.Viewlet,
|
||||
core.space.Model,
|
||||
@@ -308,16 +314,27 @@ export function createModel (builder: Builder): void {
|
||||
'modifiedOn'
|
||||
],
|
||||
options: {
|
||||
lookup: {
|
||||
owner: contact.mixin.Employee,
|
||||
category: documents.class.DocumentCategory,
|
||||
template: documents.mixin.DocumentTemplate
|
||||
}
|
||||
lookup: documentTableLookup
|
||||
}
|
||||
},
|
||||
documents.viewlet.TableDocument
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
view.class.ViewletViewAction,
|
||||
core.space.Model,
|
||||
{
|
||||
descriptor: view.viewlet.Table,
|
||||
extension: converter.extensions.CopyAsMarkdownAction,
|
||||
applicableToClass: documents.class.Document
|
||||
},
|
||||
documents.specialViewAction.TableDocument
|
||||
)
|
||||
|
||||
const documentTemplateTableLookup: Lookup<DocumentTemplate> = {
|
||||
owner: contact.mixin.Employee,
|
||||
category: documents.class.DocumentCategory
|
||||
}
|
||||
builder.createDoc(
|
||||
view.class.Viewlet,
|
||||
core.space.Model,
|
||||
@@ -367,10 +384,7 @@ export function createModel (builder: Builder): void {
|
||||
hiddenKeys: ['attachedTo']
|
||||
},
|
||||
options: {
|
||||
lookup: {
|
||||
owner: contact.mixin.Employee,
|
||||
category: documents.class.DocumentCategory
|
||||
}
|
||||
lookup: documentTemplateTableLookup
|
||||
}
|
||||
},
|
||||
documents.viewlet.TableDocumentTemplate
|
||||
|
||||
@@ -20,7 +20,7 @@ import { type ObjectSearchCategory, type ObjectSearchFactory } from '@hcengineer
|
||||
import { mergeIds, type Resource } from '@hcengineering/platform'
|
||||
import { type TagCategory } from '@hcengineering/tags'
|
||||
import { type AnyComponent } from '@hcengineering/ui/src/types'
|
||||
import { type ActionCategory, type ViewAction } from '@hcengineering/view'
|
||||
import type { ActionCategory, ViewAction, Viewlet, ViewletViewAction } from '@hcengineering/view'
|
||||
import { type NotificationType, type NotificationGroup } from '@hcengineering/notification'
|
||||
import { type TextActionVisibleFunction, type TextActionFunction } from '@hcengineering/text-editor'
|
||||
|
||||
@@ -78,11 +78,14 @@ export default mergeIds(documentsId, documents, {
|
||||
TransferDocument: '' as ViewAction,
|
||||
EditDocSpace: '' as ViewAction
|
||||
},
|
||||
specialViewAction: {
|
||||
TableDocument: '' as Ref<ViewletViewAction>
|
||||
},
|
||||
viewlet: {
|
||||
TableDocument: '' as Ref<Doc>,
|
||||
ListDocument: '' as Ref<Doc>,
|
||||
TableDocumentTemplate: '' as Ref<Doc>,
|
||||
TableDocumentDomain: '' as Ref<Doc>
|
||||
TableDocument: '' as Ref<Viewlet>,
|
||||
ListDocument: '' as Ref<Viewlet>,
|
||||
TableDocumentTemplate: '' as Ref<Viewlet>,
|
||||
TableDocumentDomain: '' as Ref<Viewlet>
|
||||
},
|
||||
notification: {
|
||||
DocumentsNotificationGroup: '' as Ref<NotificationGroup>,
|
||||
|
||||
@@ -89,7 +89,12 @@ export function createModel (builder: Builder): void {
|
||||
group: exportPlugin.ids.ImportNotificationGroup,
|
||||
txClasses: [],
|
||||
objectClass: exportPlugin.class.ExportResultRecord,
|
||||
defaultEnabled: true
|
||||
defaultEnabled: true,
|
||||
templates: {
|
||||
textTemplate: '{body}',
|
||||
htmlTemplate: '<p>{body}</p><p>{link}</p>',
|
||||
subjectTemplate: '{title}'
|
||||
}
|
||||
},
|
||||
exportPlugin.ids.ImportedDocumentsNotification
|
||||
)
|
||||
|
||||
@@ -323,14 +323,13 @@ export function createModel (builder: Builder): void {
|
||||
}
|
||||
}
|
||||
builder.createDoc(
|
||||
workbench.class.ViewletSpecialViewAction,
|
||||
view.class.ViewletViewAction,
|
||||
core.space.Model,
|
||||
{
|
||||
attachedTo: recruit.viewlet.TableCandidate,
|
||||
attachedToClass: view.class.Viewlet,
|
||||
collection: 'specialViewActions',
|
||||
descriptor: view.viewlet.Table,
|
||||
extension: workbench.extensions.SpecialViewAction,
|
||||
config: tableCandidateExportConfig
|
||||
config: tableCandidateExportConfig,
|
||||
applicableToClass: recruit.mixin.Candidate
|
||||
},
|
||||
recruit.specialViewAction.TableCandidate
|
||||
)
|
||||
|
||||
@@ -23,14 +23,14 @@ import { recruitId } from '@hcengineering/recruit'
|
||||
import recruit from '@hcengineering/recruit-resources/src/plugin'
|
||||
import { type ProjectType, type TaskTypeDescriptor } from '@hcengineering/task'
|
||||
import type { AnyComponent, Location } from '@hcengineering/ui/src/types'
|
||||
import type { ViewletSpecialViewAction } from '@hcengineering/workbench'
|
||||
import type {
|
||||
Action,
|
||||
ActionCategory,
|
||||
ViewAction,
|
||||
ViewActionAvailabilityFunction,
|
||||
ViewQueryAction,
|
||||
Viewlet
|
||||
Viewlet,
|
||||
ViewletViewAction
|
||||
} from '@hcengineering/view'
|
||||
|
||||
export default mergeIds(recruitId, recruit, {
|
||||
@@ -134,7 +134,7 @@ export default mergeIds(recruitId, recruit, {
|
||||
DefaultVacancy: '' as Ref<ProjectType>
|
||||
},
|
||||
specialViewAction: {
|
||||
TableCandidate: '' as Ref<ViewletSpecialViewAction>
|
||||
TableCandidate: '' as Ref<ViewletViewAction>
|
||||
},
|
||||
viewlet: {
|
||||
TableCandidate: '' as Ref<Viewlet>,
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
"@hcengineering/platform": "workspace:^0.7.19",
|
||||
"@hcengineering/card": "workspace:^0.7.0",
|
||||
"@hcengineering/communication": "workspace:^0.7.0",
|
||||
"@hcengineering/server-notification": "workspace:^0.7.0",
|
||||
"@hcengineering/server-card": "workspace:^0.7.0",
|
||||
"@hcengineering/server-core": "workspace:^0.7.18"
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import serverCore from '@hcengineering/server-core'
|
||||
import serverCard from '@hcengineering/server-card'
|
||||
import card from '@hcengineering/card'
|
||||
import communication from '@hcengineering/communication'
|
||||
import serverNotification from '@hcengineering/server-notification'
|
||||
|
||||
export { serverCardId } from '@hcengineering/server-card'
|
||||
|
||||
@@ -129,4 +130,8 @@ export function createModel (builder: Builder): void {
|
||||
},
|
||||
title: [['title']]
|
||||
})
|
||||
|
||||
builder.mixin(card.class.Card, core.class.Class, serverNotification.mixin.TextPresenter, {
|
||||
presenter: serverCard.function.CardTextPresenter
|
||||
})
|
||||
}
|
||||
|
||||
@@ -487,6 +487,17 @@ export function createModel (builder: Builder): void {
|
||||
|
||||
defineViewlets(builder)
|
||||
|
||||
builder.createDoc(
|
||||
view.class.ViewletViewAction,
|
||||
core.space.Model,
|
||||
{
|
||||
descriptor: view.viewlet.List,
|
||||
extension: converter.extensions.CopyAsMarkdownAction,
|
||||
applicableToClass: tracker.class.Issue
|
||||
},
|
||||
tracker.specialViewAction.IssueList
|
||||
)
|
||||
|
||||
const issuesId = 'issues'
|
||||
const componentsId = 'components'
|
||||
const milestonesId = 'milestones'
|
||||
|
||||
@@ -22,7 +22,7 @@ import { type ProjectType } from '@hcengineering/task'
|
||||
import { trackerId } from '@hcengineering/tracker'
|
||||
import tracker from '@hcengineering/tracker-resources/src/plugin'
|
||||
import type { AnyComponent } from '@hcengineering/ui/src/types'
|
||||
import { type Action, type ViewAction, type Viewlet } from '@hcengineering/view'
|
||||
import { type Action, type ViewAction, type Viewlet, type ViewletViewAction } from '@hcengineering/view'
|
||||
import { type Application } from '@hcengineering/workbench'
|
||||
|
||||
export default mergeIds(trackerId, tracker, {
|
||||
@@ -63,6 +63,9 @@ export default mergeIds(trackerId, tracker, {
|
||||
app: {
|
||||
Tracker: '' as Ref<Application>
|
||||
},
|
||||
specialViewAction: {
|
||||
IssueList: '' as Ref<ViewletViewAction>
|
||||
},
|
||||
viewlet: {
|
||||
IssueList: '' as Ref<Viewlet>,
|
||||
IssueTemplateList: '' as Ref<Viewlet>,
|
||||
|
||||
@@ -29,12 +29,17 @@ import {
|
||||
type Ref,
|
||||
type Space
|
||||
} from '@hcengineering/core'
|
||||
import { type Builder, Mixin, Model, UX } from '@hcengineering/model'
|
||||
import { type Builder, Mixin, Model, Prop, TypeRecord, TypeRef, TypeString, UX } from '@hcengineering/model'
|
||||
import core, { TClass, TDoc } from '@hcengineering/model-core'
|
||||
import preference, { TPreference } from '@hcengineering/model-preference'
|
||||
import presentation from '@hcengineering/model-presentation'
|
||||
import { type Asset, type IntlString, type Resource, type Status } from '@hcengineering/platform'
|
||||
import { type AnyComponent, type LabelAndProps, type Location } from '@hcengineering/ui/src/types'
|
||||
import { type Asset, getEmbeddedLabel, type IntlString, type Resource, type Status } from '@hcengineering/platform'
|
||||
import {
|
||||
type AnyComponent,
|
||||
type ComponentExtensionId,
|
||||
type LabelAndProps,
|
||||
type Location
|
||||
} from '@hcengineering/ui/src/types'
|
||||
import {
|
||||
type TypeEditor,
|
||||
type Action,
|
||||
@@ -99,7 +104,8 @@ import {
|
||||
type ViewOptionsModel,
|
||||
type Viewlet,
|
||||
type ViewletDescriptor,
|
||||
type ViewletPreference
|
||||
type ViewletPreference,
|
||||
type ViewletViewAction
|
||||
} from '@hcengineering/view'
|
||||
|
||||
import view from './plugin'
|
||||
@@ -318,6 +324,28 @@ export class TViewletDescriptor extends TDoc implements ViewletDescriptor {
|
||||
label!: IntlString
|
||||
}
|
||||
|
||||
@Model(view.class.ViewletViewAction, core.class.Doc, DOMAIN_MODEL)
|
||||
@UX(view.string.ViewletViewAction)
|
||||
export class TViewletViewAction extends TDoc implements ViewletViewAction {
|
||||
@Prop(TypeRef(view.class.Viewlet), getEmbeddedLabel('Viewlet'))
|
||||
declare viewlet?: Ref<Viewlet>
|
||||
|
||||
@Prop(TypeRef(view.class.ViewletDescriptor), getEmbeddedLabel('Descriptor'))
|
||||
declare descriptor?: Ref<ViewletDescriptor>
|
||||
|
||||
@Prop(TypeString(), getEmbeddedLabel('Extension'))
|
||||
declare extension: ComponentExtensionId
|
||||
|
||||
@Prop(TypeRecord(), getEmbeddedLabel('Config'))
|
||||
declare config?: Record<string, any>
|
||||
|
||||
@Prop(TypeRef(core.class.Class), getEmbeddedLabel('ApplicableToClass'))
|
||||
declare applicableToClass?: Ref<Class<Doc>>
|
||||
|
||||
@Prop(TypeRef(core.class.Class), getEmbeddedLabel('DisabledForClass'))
|
||||
declare disabledForClass?: Ref<Class<Doc>>
|
||||
}
|
||||
|
||||
@Model(view.class.Viewlet, core.class.Doc, DOMAIN_MODEL)
|
||||
export class TViewlet extends TDoc implements Viewlet {
|
||||
attachTo!: Ref<Class<Doc>>
|
||||
@@ -472,6 +500,7 @@ export function createModel (builder: Builder): void {
|
||||
TViewletPreference,
|
||||
TViewletDescriptor,
|
||||
TViewlet,
|
||||
TViewletViewAction,
|
||||
TAction,
|
||||
TActionCategory,
|
||||
TObjectValidator,
|
||||
|
||||
@@ -14,26 +14,25 @@
|
||||
//
|
||||
|
||||
import { AccountRole, type AccountUuid, type Class, DOMAIN_MODEL, type Ref, type Space } from '@hcengineering/core'
|
||||
import { type Builder, Mixin, Model, Prop, TypeRecord, TypeRef, TypeString, UX } from '@hcengineering/model'
|
||||
import { type Builder, Mixin, Model, Prop, TypeRef, UX } from '@hcengineering/model'
|
||||
import preference, { TPreference } from '@hcengineering/model-preference'
|
||||
import { createAction } from '@hcengineering/model-view'
|
||||
import core, { TAttachedDoc, TClass, TDoc } from '@hcengineering/model-core'
|
||||
import core, { TClass, TDoc } from '@hcengineering/model-core'
|
||||
import { type Asset, getEmbeddedLabel, type IntlString, type Resource } from '@hcengineering/platform'
|
||||
import view, { type KeyBinding, type Viewlet } from '@hcengineering/view'
|
||||
import view, { type KeyBinding } from '@hcengineering/view'
|
||||
import type {
|
||||
Application,
|
||||
ApplicationNavModel,
|
||||
HiddenApplication,
|
||||
SpaceView,
|
||||
ViewConfiguration,
|
||||
ViewletSpecialViewAction,
|
||||
Widget,
|
||||
WidgetPreference,
|
||||
WidgetTab,
|
||||
WidgetType,
|
||||
WorkbenchTab
|
||||
} from '@hcengineering/workbench'
|
||||
import { type AnyComponent, type ComponentExtensionId } from '@hcengineering/ui/src/types'
|
||||
import { type AnyComponent } from '@hcengineering/ui/src/types'
|
||||
import presentation from '@hcengineering/model-presentation'
|
||||
|
||||
import workbench from './plugin'
|
||||
@@ -106,29 +105,10 @@ export class TWorkbenchTab extends TPreference implements WorkbenchTab {
|
||||
isPinned!: boolean
|
||||
}
|
||||
|
||||
@Model(workbench.class.ViewletSpecialViewAction, core.class.AttachedDoc, DOMAIN_MODEL)
|
||||
@UX(workbench.string.ViewletSpecialViewAction)
|
||||
export class TViewletSpecialViewAction extends TAttachedDoc implements ViewletSpecialViewAction {
|
||||
@Prop(TypeRef(view.class.Viewlet), core.string.AttachedTo)
|
||||
declare attachedTo: Ref<Viewlet>
|
||||
|
||||
@Prop(TypeRef(view.class.Viewlet), core.string.AttachedToClass)
|
||||
declare attachedToClass: Ref<Class<Viewlet>>
|
||||
|
||||
override collection: 'specialViewActions' = 'specialViewActions'
|
||||
|
||||
@Prop(TypeString(), getEmbeddedLabel('Extension'))
|
||||
declare extension: ComponentExtensionId
|
||||
|
||||
@Prop(TypeRecord(), getEmbeddedLabel('Config'))
|
||||
declare config?: Record<string, any>
|
||||
}
|
||||
|
||||
export function createModel (builder: Builder): void {
|
||||
builder.createModel(
|
||||
TApplication,
|
||||
TSpaceView,
|
||||
TViewletSpecialViewAction,
|
||||
THiddenApplication,
|
||||
TApplicationNavModel,
|
||||
TWidget,
|
||||
|
||||
@@ -70,6 +70,7 @@
|
||||
"NewVersionConfirmation": "Chcete vytvořit novou verzi?",
|
||||
"RelationCopyDescr": "Které vztahy chcete zkopírovat?",
|
||||
"Import": "Importovat",
|
||||
"Export": "Exportovat"
|
||||
"Export": "Exportovat",
|
||||
"CardUpdated": "Karta aktualizována"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,6 +70,7 @@
|
||||
"NewVersionConfirmation": "Möchten Sie eine neue Version erstellen?",
|
||||
"RelationCopyDescr": "Welche Beziehungen möchten Sie kopieren?",
|
||||
"Import": "Importieren",
|
||||
"Export": "Exportieren"
|
||||
"Export": "Exportieren",
|
||||
"CardUpdated": "Karte aktualisiert"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,6 +70,7 @@
|
||||
"NewVersionConfirmation": "Do you want to create a new version?",
|
||||
"RelationCopyDescr": "Which relations do you want to copy?",
|
||||
"Import": "Import",
|
||||
"Export": "Export"
|
||||
"Export": "Export",
|
||||
"CardUpdated": "Card updated"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,6 +70,7 @@
|
||||
"NewVersionConfirmation": "¿Desea crear una nueva versión?",
|
||||
"RelationCopyDescr": "¿Qué relaciones quieres copiar?",
|
||||
"Import": "Importar",
|
||||
"Export": "Exportar"
|
||||
"Export": "Exportar",
|
||||
"CardUpdated": "Tarjeta actualizada"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,6 +70,7 @@
|
||||
"NewVersionConfirmation": "Voulez-vous créer une nouvelle version ?",
|
||||
"RelationCopyDescr": "Quelles relations souhaitez-vous copier ?",
|
||||
"Import": "Importer",
|
||||
"Export": "Exporter"
|
||||
"Export": "Exporter",
|
||||
"CardUpdated": "Carte mise à jour"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,6 +70,7 @@
|
||||
"NewVersionConfirmation": "Vuoi creare una nuova versione?",
|
||||
"RelationCopyDescr": "Quali relazioni vuoi copiare?",
|
||||
"Import": "Importa",
|
||||
"Export": "Esporta"
|
||||
"Export": "Esporta",
|
||||
"CardUpdated": "Scheda aggiornata"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,6 +70,7 @@
|
||||
"NewVersionConfirmation": "新しいバージョンを作成しますか?",
|
||||
"RelationCopyDescr": "どの関連をコピーしますか?",
|
||||
"Import": "インポート",
|
||||
"Export": "エクスポート"
|
||||
"Export": "エクスポート",
|
||||
"CardUpdated": "カードが更新されました"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,6 +70,7 @@
|
||||
"NewVersionConfirmation": "Deseja criar uma nova versão?",
|
||||
"RelationCopyDescr": "Quais relações você deseja copiar?",
|
||||
"Import": "Importar",
|
||||
"Export": "Exportar"
|
||||
"Export": "Exportar",
|
||||
"CardUpdated": "Cartão atualizado"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,6 +70,7 @@
|
||||
"NewVersionConfirmation": "Deseja criar uma nova versão?",
|
||||
"RelationCopyDescr": "Quais relações você deseja copiar?",
|
||||
"Import": "Importar",
|
||||
"Export": "Exportar"
|
||||
"Export": "Exportar",
|
||||
"CardUpdated": "Cartão atualizado"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,6 +70,7 @@
|
||||
"NewVersionConfirmation": "Вы хотите создать новую версию?",
|
||||
"RelationCopyDescr": "Какие связи вы хотите скопировать?",
|
||||
"Import": "Импортировать",
|
||||
"Export": "Экспортировать"
|
||||
"Export": "Экспортировать",
|
||||
"CardUpdated": "Карточка обновлена"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,6 +70,7 @@
|
||||
"NewVersionConfirmation": "Yeni bir sürüm oluşturmak istiyor musunuz?",
|
||||
"RelationCopyDescr": "Hangi ilişkileri kopyalamak istiyorsunuz?",
|
||||
"Import": "İçe aktar",
|
||||
"Export": "Dışa aktar"
|
||||
"Export": "Dışa aktar",
|
||||
"CardUpdated": "Kart güncellendi"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,6 +70,7 @@
|
||||
"NewVersionConfirmation": "您想创建一个新版本吗?",
|
||||
"RelationCopyDescr": "您想复制哪些关系?",
|
||||
"Import": "导入",
|
||||
"Export": "导出"
|
||||
"Export": "导出",
|
||||
"CardUpdated": "卡片已更新"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,6 +159,7 @@ export default mergeIds(cardId, card, {
|
||||
ForbidUpdateCard: '' as IntlString,
|
||||
ForbidCreateCardPermission: '' as IntlString,
|
||||
ForbidAddTagPermission: '' as IntlString,
|
||||
ForbidRemoveTag: '' as IntlString
|
||||
ForbidRemoveTag: '' as IntlString,
|
||||
CardUpdated: '' as IntlString
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
module.exports = {
|
||||
extends: ['./node_modules/@hcengineering/platform-rig/profiles/default/eslint.config.json'],
|
||||
parserOptions: {
|
||||
tsconfigRootDir: __dirname,
|
||||
project: './tsconfig.json'
|
||||
}
|
||||
extends: ['./node_modules/@hcengineering/platform-rig/profiles/ui/eslint.config.json'],
|
||||
parserOptions: { tsconfigRootDir: __dirname }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"trailingComma": "none",
|
||||
"tabWidth": 2,
|
||||
"semi": false,
|
||||
"singleQuote": true,
|
||||
"printWidth": 120,
|
||||
"useTabs": false,
|
||||
"bracketSpacing": true,
|
||||
"proseWrap": "preserve",
|
||||
"plugins": [
|
||||
"prettier-plugin-svelte"
|
||||
],
|
||||
"overrides": [
|
||||
{
|
||||
"files": "*.svelte",
|
||||
"options": {
|
||||
"parser": "svelte"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,21 +1,18 @@
|
||||
{
|
||||
"name": "@hcengineering/converter-resources",
|
||||
"version": "0.7.0",
|
||||
"main": "lib/index.js",
|
||||
"main": "src/index.ts",
|
||||
"svelte": "src/index.ts",
|
||||
"types": "types/index.d.ts",
|
||||
"files": [
|
||||
"lib/**/*",
|
||||
"types/**/*",
|
||||
"tsconfig.json"
|
||||
],
|
||||
"author": "Copyright © Hardcore Engineering Inc.",
|
||||
"license": "EPL-2.0",
|
||||
"scripts": {
|
||||
"build": "compile",
|
||||
"build": "compile ui",
|
||||
"format": "format src",
|
||||
"build:watch": "compile",
|
||||
"_phase:build": "compile transpile src",
|
||||
"svelte-check": "do-svelte-check",
|
||||
"_phase:svelte-check": "do-svelte-check",
|
||||
"build:watch": "compile ui",
|
||||
"_phase:build": "compile ui",
|
||||
"_phase:format": "format src",
|
||||
"_phase:validate": "compile validate",
|
||||
"_phase:test": "jest --passWithNoTests --silent",
|
||||
@@ -33,8 +30,15 @@
|
||||
"typescript": "^5.9.3",
|
||||
"eslint": "^8.54.0",
|
||||
"jest": "^29.7.0",
|
||||
"svelte-check": "^3.6.9",
|
||||
"svelte-eslint-parser": "^0.33.1",
|
||||
"svelte-loader": "^3.2.0",
|
||||
"svelte-preprocess": "^5.1.4",
|
||||
"ts-jest": "^29.1.1",
|
||||
"@types/jest": "^29.5.5"
|
||||
"@types/jest": "^29.5.5",
|
||||
"sass": "^1.80.0",
|
||||
"eslint-plugin-svelte": "^2.35.1",
|
||||
"prettier-plugin-svelte": "^3.4.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hcengineering/platform": "workspace:^0.7.19",
|
||||
@@ -45,6 +49,7 @@
|
||||
"@hcengineering/ui": "workspace:^0.7.0",
|
||||
"@hcengineering/theme": "workspace:^0.7.0",
|
||||
"@hcengineering/contact": "workspace:^0.7.0",
|
||||
"@hcengineering/view-resources": "workspace:^0.7.0"
|
||||
"@hcengineering/view-resources": "workspace:^0.7.0",
|
||||
"svelte": "^4.2.20"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
module.exports = {
|
||||
plugins: [
|
||||
require('autoprefixer')
|
||||
]
|
||||
}
|
||||
@@ -13,7 +13,8 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { buildMarkdownTableFromDocs, copyAsMarkdownTable, isIntlString } from '../index'
|
||||
import { buildMarkdownTableFromDocs, copyAsMarkdownTable } from '../markdown'
|
||||
import { isIntlString } from '../formatter'
|
||||
import type { Doc, Class, Ref } from '@hcengineering/core'
|
||||
import type { CopyAsMarkdownTableProps } from '../types'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
<!--
|
||||
// Copyright © 2026 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use it except in compliance with the License. You may obtain
|
||||
// a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
-->
|
||||
<script lang="ts">
|
||||
import type { Class, Doc, DocumentQuery, Ref } from '@hcengineering/core'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import { Button, IconCopy } from '@hcengineering/ui'
|
||||
import view from '@hcengineering/view'
|
||||
import { viewletContextStore } from '@hcengineering/view-resources'
|
||||
import { copyAsMarkdownTableFromResource, copyRelationshipTableAsMarkdown } from '../markdown/copyActions'
|
||||
|
||||
export let _class: Ref<Class<Doc>>
|
||||
export let query: DocumentQuery<Doc> = {}
|
||||
export let config: Array<string | import('@hcengineering/view').BuildModelKey> = []
|
||||
|
||||
$: ctx = $viewletContextStore?.getLastContext()
|
||||
$: relationshipTableData = ctx?.relationshipTableData
|
||||
$: viewlet = ctx?.viewlet
|
||||
|
||||
$: hasData = relationshipTableData !== undefined || (_class !== undefined && query !== undefined)
|
||||
|
||||
async function handleClick (e: MouseEvent): Promise<void> {
|
||||
if (relationshipTableData !== undefined) {
|
||||
await copyRelationshipTableAsMarkdown(e, relationshipTableData)
|
||||
} else if (_class !== undefined && query !== undefined) {
|
||||
const client = getClient()
|
||||
const docs = await client.findAll(_class, query, { limit: 1000 })
|
||||
await copyAsMarkdownTableFromResource(e, {
|
||||
cardClass: _class,
|
||||
viewlet,
|
||||
config,
|
||||
query,
|
||||
docs
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if hasData}
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<Button icon={IconCopy} label={view.string.CopyAll} on:click={handleClick} />
|
||||
{/if}
|
||||
@@ -47,10 +47,10 @@ function expandRowsForRoot (
|
||||
root: Doc,
|
||||
associationAttrs: AttributeModel[],
|
||||
pathPerAttr: string[][]
|
||||
): Array<{ docsByLevel: (Doc | undefined)[] }> {
|
||||
const rows: Array<{ docsByLevel: (Doc | undefined)[] }> = []
|
||||
): Array<{ docsByLevel: Array<Doc | undefined> }> {
|
||||
const rows: Array<{ docsByLevel: Array<Doc | undefined> }> = []
|
||||
|
||||
function expand (parent: Doc | undefined, level: number, docsSoFar: (Doc | undefined)[]): void {
|
||||
function expand (parent: Doc | undefined, level: number, docsSoFar: Array<Doc | undefined>): void {
|
||||
if (level >= associationAttrs.length) {
|
||||
rows.push({ docsByLevel: docsSoFar })
|
||||
return
|
||||
@@ -78,7 +78,7 @@ function expandRowsForRoot (
|
||||
/**
|
||||
* Compute rowSpan for a given level at each row index: how many consecutive rows share the same doc at this level.
|
||||
*/
|
||||
function computeRowSpans (rows: Array<{ docsByLevel: (Doc | undefined)[] }>, level: number): number[] {
|
||||
function computeRowSpans (rows: Array<{ docsByLevel: Array<Doc | undefined> }>, level: number): number[] {
|
||||
const spans: number[] = []
|
||||
for (let i = 0; i < rows.length; i++) {
|
||||
const doc = rows[i].docsByLevel[level]
|
||||
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
buildMarkdownTableFromDocs as buildMarkdownTableFromDocsImpl
|
||||
} from './markdown'
|
||||
import { copyAsMarkdownTableAction } from './actionImpl'
|
||||
import CopyAsMarkdownButton from './components/CopyAsMarkdownButton.svelte'
|
||||
|
||||
export * from './formatter'
|
||||
export * from './model'
|
||||
@@ -50,5 +51,8 @@ export default async (): Promise<Resources> => ({
|
||||
},
|
||||
actionImpl: {
|
||||
CopyAsMarkdownTable: copyAsMarkdownTableAction
|
||||
},
|
||||
component: {
|
||||
CopyAsMarkdownButton
|
||||
}
|
||||
})
|
||||
|
||||
@@ -15,7 +15,13 @@
|
||||
|
||||
import type { Class, Client, Doc, Hierarchy, Ref, PersonId } from '@hcengineering/core'
|
||||
import { getCurrentLanguage } from '@hcengineering/theme'
|
||||
import type { AttributeModel, BuildMarkdownTableMetadata, TableMetadata, Viewlet } from '@hcengineering/view'
|
||||
import type {
|
||||
AttributeModel,
|
||||
BuildMarkdownTableMetadata,
|
||||
TableMetadata,
|
||||
Viewlet,
|
||||
BuildModelKey
|
||||
} from '@hcengineering/view'
|
||||
import viewPlugin from '@hcengineering/view'
|
||||
import { buildConfigLookup, buildModel, getAttributeValue } from '@hcengineering/view-resources'
|
||||
import type { CopyAsMarkdownTableProps, CopyRelationshipTableAsMarkdownProps } from '../types'
|
||||
@@ -133,7 +139,7 @@ export async function buildMarkdownTableFromDocs (
|
||||
const model = await buildModel({
|
||||
client,
|
||||
_class: props.cardClass,
|
||||
keys: actualConfig.filter((key: string | import('@hcengineering/view').BuildModelKey) => {
|
||||
keys: actualConfig.filter((key: string | BuildModelKey) => {
|
||||
if (typeof key === 'string') {
|
||||
return !hiddenKeys.includes(key)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
const sveltePreprocess = require('svelte-preprocess')
|
||||
|
||||
module.exports = {
|
||||
preprocess: sveltePreprocess({
|
||||
scss: {
|
||||
// This is expected as svelte-preprocess hasn't fully migrated to the modern API yet
|
||||
silenceDeprecations: ['legacy-js-api']
|
||||
}
|
||||
})
|
||||
};
|
||||
@@ -1,11 +1,10 @@
|
||||
{
|
||||
"extends": "./node_modules/@hcengineering/platform-rig/profiles/default/tsconfig.json",
|
||||
"extends": "./node_modules/@hcengineering/platform-rig/profiles/ui/tsconfig.json",
|
||||
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"outDir": "./lib",
|
||||
"declarationDir": "./types",
|
||||
"tsBuildInfoFile": ".build/build.tsbuildinfo"
|
||||
"declarationDir": "./types"
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "lib", "dist", "types", "bundle"]
|
||||
|
||||
@@ -39,6 +39,8 @@
|
||||
"dependencies": {
|
||||
"@hcengineering/platform": "workspace:^0.7.19",
|
||||
"@hcengineering/core": "workspace:^0.7.24",
|
||||
"@hcengineering/view": "workspace:^0.7.0"
|
||||
"@hcengineering/view": "workspace:^0.7.0",
|
||||
"@hcengineering/presentation": "workspace:^0.7.0",
|
||||
"@hcengineering/ui": "workspace:^0.7.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
|
||||
import type { Client, Doc, Mixin, Ref } from '@hcengineering/core'
|
||||
import { type Plugin, type Resource, plugin } from '@hcengineering/platform'
|
||||
import type { AnyComponent, ComponentExtensionId } from '@hcengineering/ui'
|
||||
import type { ComponentPointExtension } from '@hcengineering/presentation'
|
||||
import type { Action, BuildMarkdownTableMetadata, ViewAction } from '@hcengineering/view'
|
||||
import type {
|
||||
CopyAsMarkdownTableFunction,
|
||||
@@ -39,6 +41,13 @@ export const converterPlugin = plugin(converterId, {
|
||||
(docs: Doc[], props: CopyAsMarkdownTableProps, client: Client) => Promise<string>
|
||||
>
|
||||
},
|
||||
extensions: {
|
||||
CopyAsMarkdownAction: '' as ComponentExtensionId,
|
||||
CopyAsMarkdownButton: '' as Ref<ComponentPointExtension>
|
||||
},
|
||||
component: {
|
||||
CopyAsMarkdownButton: '' as AnyComponent
|
||||
},
|
||||
action: {
|
||||
CopyAsMarkdownTable: '' as Ref<Action<Doc, Record<string, any>>>
|
||||
},
|
||||
|
||||
@@ -16,15 +16,30 @@
|
||||
<script lang="ts">
|
||||
import { getEmbeddedLabel } from '@hcengineering/platform'
|
||||
import { ApproveRequest } from '@hcengineering/process'
|
||||
import { tooltip } from '@hcengineering/ui'
|
||||
import { getUserTimezone, tooltip } from '@hcengineering/ui'
|
||||
import { BooleanPresenter } from '@hcengineering/view-resources'
|
||||
|
||||
export let value: ApproveRequest
|
||||
|
||||
export function formatSignatureDate (date: number): string {
|
||||
const timeZone: string = getUserTimezone()
|
||||
|
||||
return new Date(date).toLocaleDateString('default', {
|
||||
year: 'numeric',
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
timeZone,
|
||||
timeZoneName: 'short',
|
||||
hour: 'numeric',
|
||||
minute: 'numeric',
|
||||
second: 'numeric'
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<div
|
||||
use:tooltip={value.approved !== undefined
|
||||
? { label: getEmbeddedLabel(value.user + ' ' + new Date(value.modifiedOn).toLocaleString()) }
|
||||
? { label: getEmbeddedLabel(value.user + ' ' + formatSignatureDate(value.modifiedOn)) }
|
||||
: undefined}
|
||||
>
|
||||
<BooleanPresenter value={value.approved} />
|
||||
|
||||
@@ -45,6 +45,7 @@
|
||||
{viewlets}
|
||||
{label}
|
||||
{space}
|
||||
{resultQuery}
|
||||
{modeSelectorProps}
|
||||
>
|
||||
<svelte:fragment slot="header-tools">
|
||||
|
||||
@@ -113,6 +113,7 @@
|
||||
"Or": "Nebo",
|
||||
"HyperlinkPlaceholder": "https://jappleseed.com",
|
||||
"CopyToClipboard": "Kopírovat do schránky",
|
||||
"CopyAll": "Kopírovat vše",
|
||||
"CopyDocumentMarkdown": "Kopírovat jako Markdown",
|
||||
"CopyAsMarkdownTable": "Kopírovat jako Markdown tabulka",
|
||||
"TableCopiedToClipboard": "Tabulka zkopírována do schránky",
|
||||
|
||||
@@ -113,6 +113,7 @@
|
||||
"Or": "Oder",
|
||||
"HyperlinkPlaceholder": "https://jappleseed.com",
|
||||
"CopyToClipboard": "In die Zwischenablage kopieren",
|
||||
"CopyAll": "Alle kopieren",
|
||||
"CopyDocumentMarkdown": "Als Markdown kopieren",
|
||||
"CopyAsMarkdownTable": "Als Markdown-Tabelle kopieren",
|
||||
"TableCopiedToClipboard": "Tabelle in die Zwischenablage kopiert",
|
||||
|
||||
@@ -113,6 +113,7 @@
|
||||
"Or": "Or",
|
||||
"HyperlinkPlaceholder": "https://jappleseed.com",
|
||||
"CopyToClipboard": "Copy to clipboard",
|
||||
"CopyAll": "Copy All",
|
||||
"CopyDocumentMarkdown": "Copy as Markdown",
|
||||
"CopyAsMarkdownTable": "Copy as Markdown Table",
|
||||
"TableCopiedToClipboard": "Table copied to clipboard",
|
||||
|
||||
@@ -109,6 +109,7 @@
|
||||
"Or": "O",
|
||||
"HyperlinkPlaceholder": "https://jappleseed.com",
|
||||
"CopyToClipboard": "Copiar al portapapeles",
|
||||
"CopyAll": "Copiar todo",
|
||||
"CopyDocumentMarkdown": "Copiar como Markdown",
|
||||
"CopyAsMarkdownTable": "Copiar como tabla Markdown",
|
||||
"TableCopiedToClipboard": "Tabla copiada al portapapeles",
|
||||
|
||||
@@ -109,6 +109,7 @@
|
||||
"Or": "Ou",
|
||||
"HyperlinkPlaceholder": "https://jappleseed.com",
|
||||
"CopyToClipboard": "Copier dans le presse-papiers",
|
||||
"CopyAll": "Tout copier",
|
||||
"CopyDocumentMarkdown": "Copier en Markdown",
|
||||
"CopyAsMarkdownTable": "Copier en tableau Markdown",
|
||||
"TableCopiedToClipboard": "Tableau copié dans le presse-papiers",
|
||||
|
||||
@@ -109,6 +109,7 @@
|
||||
"Or": "O",
|
||||
"HyperlinkPlaceholder": "https://jappleseed.com",
|
||||
"CopyToClipboard": "Copia negli appunti",
|
||||
"CopyAll": "Copia tutto",
|
||||
"CopyDocumentMarkdown": "Copia come Markdown",
|
||||
"CopyAsMarkdownTable": "Copia come tabella Markdown",
|
||||
"TableCopiedToClipboard": "Tabella copiata negli appunti",
|
||||
|
||||
@@ -109,6 +109,7 @@
|
||||
"Or": "または",
|
||||
"HyperlinkPlaceholder": "https://jappleseed.com",
|
||||
"CopyToClipboard": "クリップボードにコピー",
|
||||
"CopyAll": "すべてコピー",
|
||||
"CopyDocumentMarkdown": "Markdownとしてコピー",
|
||||
"CopyAsMarkdownTable": "Markdownテーブルとしてコピー",
|
||||
"TableCopiedToClipboard": "テーブルをクリップボードにコピーしました",
|
||||
|
||||
@@ -109,6 +109,7 @@
|
||||
"Or": "Ou",
|
||||
"HyperlinkPlaceholder": "https://jappleseed.com",
|
||||
"CopyToClipboard": "Copiar para área de transferência",
|
||||
"CopyAll": "Copiar tudo",
|
||||
"CopyDocumentMarkdown": "Copiar como Markdown",
|
||||
"CopyAsMarkdownTable": "Copiar como tabela Markdown",
|
||||
"TableCopiedToClipboard": "Tabela copiada para área de transferência",
|
||||
|
||||
@@ -109,6 +109,7 @@
|
||||
"Or": "Ou",
|
||||
"HyperlinkPlaceholder": "https://jappleseed.com",
|
||||
"CopyToClipboard": "Copiar para área de transferência",
|
||||
"CopyAll": "Copiar tudo",
|
||||
"CopyDocumentMarkdown": "Copiar como Markdown",
|
||||
"CopyAsMarkdownTable": "Copiar como tabela Markdown",
|
||||
"TableCopiedToClipboard": "Tabela copiada para área de transferência",
|
||||
|
||||
@@ -109,6 +109,7 @@
|
||||
"Or": "Или",
|
||||
"HyperlinkPlaceholder": "https://jappleseed.com",
|
||||
"CopyToClipboard": "Скопировать в буфер обмена",
|
||||
"CopyAll": "Копировать всё",
|
||||
"CopyDocumentMarkdown": "Скопировать как Markdown",
|
||||
"CopyAsMarkdownTable": "Скопировать как таблицу Markdown",
|
||||
"TableCopiedToClipboard": "Таблица скопирована в буфер обмена",
|
||||
|
||||
@@ -109,6 +109,7 @@
|
||||
"Or": "Veya",
|
||||
"HyperlinkPlaceholder": "https://ornek.com",
|
||||
"CopyToClipboard": "Panoya kopyala",
|
||||
"CopyAll": "Tümünü kopyala",
|
||||
"CopyDocumentMarkdown": "Markdown olarak kopyala",
|
||||
"CopyAsMarkdownTable": "Markdown tablosu olarak kopyala",
|
||||
"TableCopiedToClipboard": "Tablo panoya kopyalandı",
|
||||
|
||||
@@ -113,6 +113,7 @@
|
||||
"Or": "或",
|
||||
"HyperlinkPlaceholder": "https://jappleseed.com",
|
||||
"CopyToClipboard": "复制到剪贴板",
|
||||
"CopyAll": "复制全部",
|
||||
"CopyDocumentMarkdown": "复制为 Markdown",
|
||||
"CopyAsMarkdownTable": "复制为 Markdown 表格",
|
||||
"TableCopiedToClipboard": "表格已复制到剪贴板",
|
||||
|
||||
@@ -34,7 +34,6 @@
|
||||
import { createQuery, getClient, reduceCalls, updateAttribute } from '@hcengineering/presentation'
|
||||
import ui, {
|
||||
Button,
|
||||
IconCopy,
|
||||
Label,
|
||||
Loading,
|
||||
eventToHTMLElement,
|
||||
@@ -44,14 +43,14 @@
|
||||
} from '@hcengineering/ui'
|
||||
import { AttributeModel, BuildModelKey, BuildModelOptions, ViewOptionModel, ViewOptions } from '@hcengineering/view'
|
||||
import { deepEqual } from 'fast-equals'
|
||||
import { createEventDispatcher, onMount } from 'svelte'
|
||||
import { createEventDispatcher, onDestroy, onMount } from 'svelte'
|
||||
import { Readable } from 'svelte/store'
|
||||
import { showMenu } from '../actions'
|
||||
import { canChangeAttribute } from '../permissions'
|
||||
import view from '../plugin'
|
||||
import { ViewletContextStore, viewletContextStore } from '../viewletContextStore'
|
||||
import { buildConfigAssociation, buildConfigLookup, buildModel, getAttributeValue, restrictionStore } from '../utils'
|
||||
import { getResultOptions, getResultQuery } from '../viewOptions'
|
||||
import converter from '@hcengineering/converter'
|
||||
import IconUpDown from './icons/UpDown.svelte'
|
||||
import RelationsSelectorPopup from './RelationsSelectorPopup.svelte'
|
||||
|
||||
@@ -539,16 +538,38 @@
|
||||
)
|
||||
}
|
||||
|
||||
async function handleCopyAsMarkdown (e: MouseEvent): Promise<void> {
|
||||
if (model === undefined || viewModel.length === 0) return
|
||||
const copyFn = await getResource(converter.function.CopyRelationshipAsMarkdown)
|
||||
await copyFn(e, {
|
||||
viewModel,
|
||||
model,
|
||||
objects,
|
||||
cardClass: _class
|
||||
$: relationshipTableData =
|
||||
model !== undefined && viewModel.length > 0 ? { viewModel, model, objects, cardClass: _class } : undefined
|
||||
|
||||
$: {
|
||||
viewletContextStore.update((cur) => {
|
||||
const contexts = cur.contexts
|
||||
const last = contexts[contexts.length - 1]
|
||||
if (last === undefined) return cur
|
||||
const updated =
|
||||
relationshipTableData !== undefined
|
||||
? { ...last, relationshipTableData }
|
||||
: (() => {
|
||||
const rest = { ...last }
|
||||
delete rest.relationshipTableData
|
||||
return rest
|
||||
})()
|
||||
return new ViewletContextStore([...contexts.slice(0, -1), updated])
|
||||
})
|
||||
}
|
||||
|
||||
onDestroy(() => {
|
||||
viewletContextStore.update((cur) => {
|
||||
const contexts = cur.contexts
|
||||
const last = contexts[contexts.length - 1]
|
||||
if (last?.relationshipTableData !== undefined) {
|
||||
const rest = { ...last }
|
||||
delete rest.relationshipTableData
|
||||
return new ViewletContextStore([...contexts.slice(0, -1), rest])
|
||||
}
|
||||
return cur
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
{#if !model || isBuildingModel}
|
||||
@@ -677,19 +698,6 @@
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
{#if objects.length > 0 && viewModel.length > 0 && model !== undefined}
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div class="px-1">
|
||||
<Button
|
||||
icon={IconCopy}
|
||||
label={view.string.CopyToClipboard}
|
||||
kind={'ghost'}
|
||||
size={'small'}
|
||||
on:click={handleCopyAsMarkdown}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
import { Class, Doc, DocumentQuery, Ref, Space, WithLookup } from '@hcengineering/core'
|
||||
import { IModeSelector, ModeSelector, SearchInput, Header, Breadcrumb, HeaderAdaptive } from '@hcengineering/ui'
|
||||
import type { Asset } from '@hcengineering/platform'
|
||||
import { Viewlet } from '@hcengineering/view'
|
||||
import { ComponentExtensions, getClient } from '@hcengineering/presentation'
|
||||
import view, { Viewlet, type ViewletViewAction } from '@hcengineering/view'
|
||||
import { getViewletSpecialActions } from '../viewletUtils'
|
||||
import ViewletSelector from './ViewletSelector.svelte'
|
||||
import FilterButton from './filter/FilterButton.svelte'
|
||||
|
||||
@@ -17,8 +19,11 @@
|
||||
export let showLabelSelector = false
|
||||
export let modeSelectorProps: IModeSelector | undefined = undefined
|
||||
export let adaptive: HeaderAdaptive = 'doubleRow'
|
||||
export let resultQuery: DocumentQuery<Doc> = {}
|
||||
|
||||
let scroller: HTMLElement
|
||||
|
||||
$: viewletActions = viewlet != null ? getViewletSpecialActions(getClient(), viewlet) : []
|
||||
</script>
|
||||
|
||||
<Header
|
||||
@@ -48,6 +53,16 @@
|
||||
<FilterButton {_class} {space} />
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="actions">
|
||||
{#each viewletActions as action (action._id)}
|
||||
<ComponentExtensions
|
||||
extension={action.extension}
|
||||
props={{
|
||||
_class,
|
||||
query: resultQuery,
|
||||
config: action.config ?? {}
|
||||
}}
|
||||
/>
|
||||
{/each}
|
||||
<slot name="actions" />
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="extra">
|
||||
|
||||
@@ -16,7 +16,9 @@
|
||||
SearchInput,
|
||||
themeStore
|
||||
} from '@hcengineering/ui'
|
||||
import { ViewOptions, Viewlet, ViewletPreference } from '@hcengineering/view'
|
||||
import view, { ViewOptions, Viewlet, ViewletPreference, type ViewletViewAction } from '@hcengineering/view'
|
||||
import { ComponentExtensions, getClient } from '@hcengineering/presentation'
|
||||
import { getViewletSpecialActions } from '../viewletUtils'
|
||||
|
||||
import FilterBar from './filter/FilterBar.svelte'
|
||||
import FilterButton from './filter/FilterButton.svelte'
|
||||
@@ -51,6 +53,8 @@
|
||||
$: searchQuery = search === '' ? { ...query } : { ...query, $search: search }
|
||||
|
||||
$: hideExtra = modeSelectorProps === undefined && $$slots.extra === undefined
|
||||
|
||||
$: viewletActions = viewlet != null ? getViewletSpecialActions(getClient(), viewlet) : []
|
||||
</script>
|
||||
|
||||
<Header {adaptive} {hideActions} {hideExtra} overflowExtra>
|
||||
@@ -67,6 +71,16 @@
|
||||
<FilterButton {_class} />
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="actions">
|
||||
{#each viewletActions as action (action._id)}
|
||||
<ComponentExtensions
|
||||
extension={action.extension}
|
||||
props={{
|
||||
_class,
|
||||
query: resultQuery,
|
||||
config: action.config ?? {}
|
||||
}}
|
||||
/>
|
||||
{/each}
|
||||
<slot name="actions" />
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="extra">
|
||||
|
||||
@@ -217,6 +217,7 @@ export {
|
||||
} from './utils'
|
||||
export * from './viewOptions'
|
||||
export * from './viewletContextStore'
|
||||
export { getViewletSpecialActions } from './viewletUtils'
|
||||
export { copyMarkdown } from './actionImpl'
|
||||
export { default as SimpleNotification } from './components/SimpleNotification.svelte'
|
||||
export type { BuildMarkdownTableMetadata } from '@hcengineering/view'
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { type Class, type Doc, type DocumentQuery, type Ref } from '@hcengineering/core'
|
||||
import { type BuildModelKey, type Viewlet, type ViewOptions } from '@hcengineering/view'
|
||||
import { writable } from 'svelte/store'
|
||||
import type { CopyRelationshipTableAsMarkdownProps } from '@hcengineering/converter'
|
||||
|
||||
/**
|
||||
* Context for the current viewlet table being displayed
|
||||
@@ -14,6 +15,7 @@ export interface ViewletContext {
|
||||
query?: DocumentQuery<Doc>
|
||||
viewOptions?: ViewOptions
|
||||
_class?: Ref<Class<Doc>>
|
||||
relationshipTableData?: CopyRelationshipTableAsMarkdownProps
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
//
|
||||
// Copyright © 2026 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.
|
||||
//
|
||||
|
||||
import type { Client, Ref } from '@hcengineering/core'
|
||||
import view, { type Viewlet, type ViewletViewAction } from '@hcengineering/view'
|
||||
|
||||
/** Shape used by the header UI; both viewlet- and descriptor-scoped actions provide this. */
|
||||
export interface ResolvedViewletSpecialAction {
|
||||
_id: Ref<ViewletViewAction>
|
||||
extension: ViewletViewAction['extension']
|
||||
config?: Record<string, any>
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve special view actions for a viewlet by:
|
||||
* 1. One query: ViewletViewAction where viewlet is in (current + template viewlets) OR descriptor matches.
|
||||
* 2. For rows with descriptor set, filter by applicableToClass and disabledForClass.
|
||||
* Actions are deduplicated by extension (viewlet-scoped take precedence).
|
||||
*/
|
||||
export function getViewletSpecialActions (client: Client, viewlet: Viewlet): ResolvedViewletSpecialAction[] {
|
||||
const model = client.getModel()
|
||||
const hierarchy = client.getHierarchy()
|
||||
|
||||
let actions = model.findAllSync(view.class.ViewletViewAction, {
|
||||
viewlet: viewlet._id
|
||||
}) as ViewletViewAction[]
|
||||
if (actions == null || actions.length === 0) {
|
||||
actions = model.findAllSync(view.class.ViewletViewAction, {
|
||||
descriptor: viewlet.descriptor
|
||||
}) as ViewletViewAction[]
|
||||
}
|
||||
|
||||
if (actions == null || actions.length === 0) {
|
||||
return []
|
||||
}
|
||||
|
||||
const filtered = actions.filter((a) => {
|
||||
if (a.descriptor != null) {
|
||||
if (a.applicableToClass != null && !hierarchy.isDerived(viewlet.attachTo, a.applicableToClass)) {
|
||||
return false
|
||||
}
|
||||
if (a.disabledForClass != null && hierarchy.isDerived(viewlet.attachTo, a.disabledForClass)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
const seen = new Set<string>()
|
||||
const result: ResolvedViewletSpecialAction[] = []
|
||||
const byViewlet = filtered.filter((a) => a.viewlet != null)
|
||||
const byDescriptor = filtered.filter((a) => a.descriptor != null)
|
||||
for (const a of [...byViewlet, ...byDescriptor]) {
|
||||
if (seen.has(a.extension)) continue
|
||||
seen.add(a.extension)
|
||||
result.push({ _id: a._id, extension: a.extension, config: a.config })
|
||||
}
|
||||
return result
|
||||
}
|
||||
@@ -66,7 +66,8 @@ import {
|
||||
ViewAction,
|
||||
Viewlet,
|
||||
ViewletDescriptor,
|
||||
ViewletPreference
|
||||
ViewletPreference,
|
||||
ViewletViewAction
|
||||
} from './types'
|
||||
|
||||
export * from './types'
|
||||
@@ -126,6 +127,7 @@ const view = plugin(viewId, {
|
||||
ViewletPreference: '' as Ref<Class<ViewletPreference>>,
|
||||
ViewletDescriptor: '' as Ref<Class<ViewletDescriptor>>,
|
||||
Viewlet: '' as Ref<Class<Viewlet>>,
|
||||
ViewletViewAction: '' as Ref<Class<ViewletViewAction>>,
|
||||
Action: '' as Ref<Class<Action>>,
|
||||
ActionCategory: '' as Ref<Class<ActionCategory>>,
|
||||
LinkPresenter: '' as Ref<Class<LinkPresenter>>,
|
||||
@@ -224,8 +226,10 @@ const view = plugin(viewId, {
|
||||
Subscribed: '' as IntlString,
|
||||
HyperlinkPlaceholder: '' as IntlString,
|
||||
CopyToClipboard: '' as IntlString,
|
||||
CopyAll: '' as IntlString,
|
||||
NoGrouping: '' as IntlString,
|
||||
Type: '' as IntlString,
|
||||
ViewletViewAction: '' as IntlString,
|
||||
UnArchive: '' as IntlString,
|
||||
Archive: '' as IntlString,
|
||||
Save: '' as IntlString,
|
||||
|
||||
@@ -46,6 +46,7 @@ import { Preference } from '@hcengineering/preference'
|
||||
import {
|
||||
AnyComponent,
|
||||
AnySvelteComponent,
|
||||
type ComponentExtensionId,
|
||||
Location,
|
||||
Location as PlatformLocation,
|
||||
type LabelAndProps
|
||||
@@ -458,6 +459,24 @@ export interface ViewletConfigOptions {
|
||||
sortable?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Special view action shown in the viewlet header. Exactly one of viewlet or descriptor must be set.
|
||||
* When descriptor is set, scope is filtered by applicableToClass / disabledForClass.
|
||||
* @public
|
||||
*/
|
||||
export interface ViewletViewAction extends Doc {
|
||||
/** When set, action applies to this viewlet (and template viewlets). */
|
||||
viewlet?: Ref<Viewlet>
|
||||
/** When set, action applies to viewlets with this descriptor; use applicableToClass / disabledForClass to scope. */
|
||||
descriptor?: Ref<ViewletDescriptor>
|
||||
extension: ComponentExtensionId
|
||||
config?: Record<string, any>
|
||||
/** When descriptor is set: show only when viewlet.attachTo is this class or a subclass. */
|
||||
applicableToClass?: Ref<Class<Doc>>
|
||||
/** When descriptor is set: hide when viewlet.attachTo is this class or a subclass. */
|
||||
disabledForClass?: Ref<Class<Doc>>
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
|
||||
@@ -36,10 +36,11 @@
|
||||
FilterButton,
|
||||
getResultOptions,
|
||||
getResultQuery,
|
||||
getViewletSpecialActions,
|
||||
ViewletSelector,
|
||||
ViewletSettingButton
|
||||
} from '@hcengineering/view-resources'
|
||||
import workbench, { type ParentsNavigationModel, type ViewletSpecialViewAction } from '@hcengineering/workbench'
|
||||
import workbench, { type ParentsNavigationModel } from '@hcengineering/workbench'
|
||||
import ComponentNavigator from './ComponentNavigator.svelte'
|
||||
import { deepEqual } from 'fast-equals'
|
||||
import { ComponentType } from 'svelte'
|
||||
@@ -94,12 +95,7 @@
|
||||
$: void updateQuery(_baseQuery, viewOptions, viewlet, queryBuilder)
|
||||
$: void updateOptions(viewlet?.options, viewOptions, viewlet)
|
||||
|
||||
$: viewletActions =
|
||||
viewlet != null
|
||||
? (client.getModel().findAllSync(workbench.class.ViewletSpecialViewAction, {
|
||||
attachedTo: viewlet._id
|
||||
}) as ViewletSpecialViewAction[])
|
||||
: []
|
||||
$: viewletActions = viewlet != null ? getViewletSpecialActions(client, viewlet) : []
|
||||
|
||||
async function updateOptions (
|
||||
_options: FindOptions<Doc> | undefined,
|
||||
@@ -141,7 +137,9 @@
|
||||
|
||||
<Header
|
||||
adaptive={modes !== undefined ? 'doubleRow' : filterVisible ? 'freezeActions' : 'disabled'}
|
||||
hideActions={!(createLabel && createComponent) && createButton === undefined}
|
||||
hideActions={!(createLabel && createComponent) &&
|
||||
createButton === undefined &&
|
||||
(viewletActions == null || viewletActions.length === 0)}
|
||||
hideExtra={modes === undefined}
|
||||
freezeBefore
|
||||
>
|
||||
|
||||
@@ -17,7 +17,6 @@ import { workbenchId, workbenchPlugin } from './plugin'
|
||||
|
||||
export * from './analytics'
|
||||
export * from './types'
|
||||
export type { ViewletSpecialViewAction } from './types'
|
||||
export * from './utils'
|
||||
|
||||
export { workbenchId }
|
||||
|
||||
@@ -25,7 +25,6 @@ import type {
|
||||
ApplicationNavModel,
|
||||
HiddenApplication,
|
||||
SpaceView,
|
||||
ViewletSpecialViewAction,
|
||||
Widget,
|
||||
WidgetPreference,
|
||||
WidgetTab,
|
||||
@@ -43,8 +42,7 @@ export const workbenchPlugin = plugin(workbenchId, {
|
||||
HiddenApplication: '' as Ref<Class<HiddenApplication>>,
|
||||
Widget: '' as Ref<Class<Widget>>,
|
||||
WidgetPreference: '' as Ref<Class<WidgetPreference>>,
|
||||
WorkbenchTab: '' as Ref<Class<WorkbenchTab>>,
|
||||
ViewletSpecialViewAction: '' as Ref<Class<ViewletSpecialViewAction>>
|
||||
WorkbenchTab: '' as Ref<Class<WorkbenchTab>>
|
||||
},
|
||||
mixin: {
|
||||
SpaceView: '' as Ref<Mixin<SpaceView>>
|
||||
@@ -61,7 +59,6 @@ export const workbenchPlugin = plugin(workbenchId, {
|
||||
string: {
|
||||
Archive: '' as IntlString,
|
||||
View: '' as IntlString,
|
||||
ViewletSpecialViewAction: '' as IntlString,
|
||||
ServerUnderMaintenance: '' as IntlString,
|
||||
UpgradeDownloadProgress: '' as IntlString,
|
||||
OpenInSidebar: '' as IntlString,
|
||||
|
||||
@@ -14,28 +14,11 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import type {
|
||||
AccountRole,
|
||||
AccountUuid,
|
||||
AttachedDoc,
|
||||
Class,
|
||||
Doc,
|
||||
DocumentQuery,
|
||||
Obj,
|
||||
Ref,
|
||||
Space
|
||||
} from '@hcengineering/core'
|
||||
import type { Viewlet } from '@hcengineering/view'
|
||||
import type { AccountRole, AccountUuid, Class, Doc, DocumentQuery, Obj, Ref, Space } from '@hcengineering/core'
|
||||
import { DocNotifyContext, InboxNotification } from '@hcengineering/notification'
|
||||
import type { Asset, IntlString, Resource } from '@hcengineering/platform'
|
||||
import type { Preference } from '@hcengineering/preference'
|
||||
import {
|
||||
AnyComponent,
|
||||
type AnySvelteComponent,
|
||||
type ComponentExtensionId,
|
||||
Location,
|
||||
ResolvedLocation
|
||||
} from '@hcengineering/ui'
|
||||
import { AnyComponent, type AnySvelteComponent, Location, ResolvedLocation } from '@hcengineering/ui'
|
||||
|
||||
/** @public */
|
||||
export interface LocationData {
|
||||
@@ -229,9 +212,3 @@ export interface ViewConfiguration {
|
||||
export interface SpaceView extends Class<Obj> {
|
||||
view: ViewConfiguration
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export interface ViewletSpecialViewAction extends AttachedDoc<Viewlet, 'specialViewActions'> {
|
||||
extension: ComponentExtensionId
|
||||
config?: Record<string, any>
|
||||
}
|
||||
|
||||
@@ -876,8 +876,17 @@ export async function OnCardTag (ctx: TxMixin<Card, Card>[], control: TriggerCon
|
||||
return res
|
||||
}
|
||||
|
||||
export async function CardTextPresenter (doc: Doc): Promise<string> {
|
||||
const card = doc as Card
|
||||
|
||||
return card.title
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
||||
export default async () => ({
|
||||
function: {
|
||||
CardTextPresenter
|
||||
},
|
||||
trigger: {
|
||||
OnAttribute,
|
||||
OnAttributeRemove,
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@hcengineering/core": "workspace:^0.7.24",
|
||||
"@hcengineering/server-notification": "workspace:^0.7.0",
|
||||
"@hcengineering/server-core": "workspace:^0.7.18",
|
||||
"@hcengineering/platform": "workspace:^0.7.19"
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
import type { Metadata, Plugin, Resource } from '@hcengineering/platform'
|
||||
import { plugin } from '@hcengineering/platform'
|
||||
import type { TriggerFunc } from '@hcengineering/server-core'
|
||||
import { type Presenter } from '@hcengineering/server-notification'
|
||||
|
||||
/**
|
||||
* @public
|
||||
@@ -29,6 +30,9 @@ export default plugin(serverCardId, {
|
||||
metadata: {
|
||||
CommunicationEnabled: '' as Metadata<boolean>
|
||||
},
|
||||
function: {
|
||||
CardTextPresenter: '' as Resource<Presenter>
|
||||
},
|
||||
trigger: {
|
||||
OnAttribute: '' as Resource<TriggerFunc>,
|
||||
OnAttributeRemove: '' as Resource<TriggerFunc>,
|
||||
|
||||
@@ -44,7 +44,10 @@ import serverNotification from '@hcengineering/server-notification'
|
||||
import {
|
||||
AvailableProvidersCache,
|
||||
AvailableProvidersCacheKey,
|
||||
getContentByTemplate
|
||||
getContentByTemplate,
|
||||
getNotificationProviderControl,
|
||||
getReceiversInfo,
|
||||
getAllowedProviders
|
||||
} from '@hcengineering/server-notification-resources'
|
||||
import { getMetadata } from '@hcengineering/platform'
|
||||
import activity, { ActivityMessage } from '@hcengineering/activity'
|
||||
@@ -156,7 +159,7 @@ async function notifyByEmail (
|
||||
senderSocialId: PersonId,
|
||||
email: string,
|
||||
data: InboxNotification,
|
||||
message: ActivityMessage
|
||||
message?: ActivityMessage
|
||||
): Promise<void> {
|
||||
let senderName = sender !== undefined ? formatName(sender.name, control.branding?.lastNameFirst) : ''
|
||||
if (senderName === '' && senderSocialId === core.account.System) {
|
||||
@@ -166,6 +169,12 @@ async function notifyByEmail (
|
||||
|
||||
if (content !== undefined) {
|
||||
await sendEmailNotification(control.ctx, content.text, content.html, content.subject, email)
|
||||
} else {
|
||||
control.ctx.info('notifyByEmail: getContentByTemplate returned undefined, email not sent', {
|
||||
notificationId: data._id,
|
||||
type,
|
||||
docClass: doc._class
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -197,7 +206,10 @@ async function processEmailNotifications (control: TriggerControl, notifications
|
||||
const docId = notifications[0].objectId
|
||||
const docClass = notifications[0].objectClass
|
||||
const doc = (await control.findAll(control.ctx, docClass, { _id: docId }))[0]
|
||||
if (doc === undefined) return
|
||||
if (doc === undefined) {
|
||||
control.ctx.info('processEmailNotifications: doc not found', { docId, docClass })
|
||||
return
|
||||
}
|
||||
const messages = await getNotificationMessages(notifications, control)
|
||||
const { hierarchy } = control
|
||||
|
||||
@@ -205,7 +217,10 @@ async function processEmailNotifications (control: TriggerControl, notifications
|
||||
|
||||
for (const n of notifications) {
|
||||
const type = (n.types ?? [])[0]
|
||||
if (type === undefined) continue
|
||||
if (type === undefined) {
|
||||
control.ctx.info('processEmailNotifications: skipping notification without type', { notificationId: n._id })
|
||||
continue
|
||||
}
|
||||
let message: ActivityMessage | undefined
|
||||
if (hierarchy.isDerived(n._class, notification.class.ActivityInboxNotification)) {
|
||||
const activityNotification = n as ActivityInboxNotification
|
||||
@@ -215,11 +230,22 @@ async function processEmailNotifications (control: TriggerControl, notifications
|
||||
if (hierarchy.isDerived(mentionNotification.mentionedInClass, activity.class.ActivityMessage)) {
|
||||
message = messages.find((m) => m._id === mentionNotification.mentionedIn)
|
||||
}
|
||||
} else if (hierarchy.isDerived(n._class, notification.class.CommonInboxNotification)) {
|
||||
message = undefined
|
||||
}
|
||||
|
||||
if (message === undefined) continue
|
||||
if (message === undefined && !hierarchy.isDerived(n._class, notification.class.CommonInboxNotification)) {
|
||||
control.ctx.info('processEmailNotifications: skipping - no ActivityMessage and not CommonInboxNotification', {
|
||||
notificationId: n._id,
|
||||
notificationClass: n._class
|
||||
})
|
||||
continue
|
||||
}
|
||||
const employee = await getEmployeeByAcc(control, n.user)
|
||||
if (employee === undefined) continue
|
||||
if (employee === undefined) {
|
||||
control.ctx.info('processEmailNotifications: no employee for user', { notificationId: n._id, user: n.user })
|
||||
continue
|
||||
}
|
||||
const emailQuery = {
|
||||
attachedTo: employee._id,
|
||||
type: { $in: [SocialIdType.EMAIL, SocialIdType.GOOGLE] },
|
||||
@@ -247,30 +273,85 @@ async function processEmailNotifications (control: TriggerControl, notifications
|
||||
continue
|
||||
}
|
||||
|
||||
const senderSocialId = message.createdBy ?? message.modifiedBy
|
||||
const senderSocialId = message !== undefined ? (message.createdBy ?? message.modifiedBy) : core.account.System
|
||||
const sender = senders.get(senderSocialId) ?? (await getPerson(control, senderSocialId))
|
||||
if (sender != null) {
|
||||
senders.set(senderSocialId, sender)
|
||||
}
|
||||
|
||||
control.ctx.info('processEmailNotifications: sending email', {
|
||||
notificationId: n._id,
|
||||
type,
|
||||
notificationClass: n._class
|
||||
})
|
||||
await notifyByEmail(control, type, doc, sender, senderSocialId, emails[0].value, n, message)
|
||||
}
|
||||
}
|
||||
|
||||
function hasEmailProvider (n: InboxNotification, availableProviders: AvailableProvidersCache): boolean {
|
||||
const providers = availableProviders.get(n._id) ?? availableProviders.get(n.objectId as Ref<InboxNotification>)
|
||||
return providers?.find((p) => p === gmail.providers.EmailNotificationProvider) !== undefined
|
||||
}
|
||||
|
||||
async function NotificationsHandler (txes: TxCreateDoc<InboxNotification>[], control: TriggerControl): Promise<Tx[]> {
|
||||
control.ctx.info('NotificationsHandler: received InboxNotification txes', {
|
||||
count: txes.length,
|
||||
workspace: control.workspace?.url,
|
||||
objectClasses: [...new Set(txes.map((tx) => tx.objectClass))]
|
||||
})
|
||||
|
||||
const availableProviders: AvailableProvidersCache = control.contextCache.get(AvailableProvidersCacheKey) ?? new Map()
|
||||
|
||||
const all: InboxNotification[] = txes
|
||||
.map((tx) => TxProcessor.createDoc2Doc(tx))
|
||||
.filter(
|
||||
(it) => availableProviders.get(it._id)?.find((p) => p === gmail.providers.EmailNotificationProvider) !== undefined
|
||||
)
|
||||
const all: InboxNotification[] = txes.map((tx) => TxProcessor.createDoc2Doc(tx))
|
||||
|
||||
if (all.length === 0) {
|
||||
const notificationsWithEmail = all.filter((it) => hasEmailProvider(it, availableProviders))
|
||||
|
||||
control.ctx.info('NotificationsHandler: processing inbox notifications', {
|
||||
total: all.length,
|
||||
withEmailFromCache: notificationsWithEmail.length,
|
||||
notificationClasses: [...new Set(all.map((n) => n._class))],
|
||||
notificationTypes: [...new Set(all.flatMap((n) => n.types ?? []))]
|
||||
})
|
||||
|
||||
if (notificationsWithEmail.length < all.length) {
|
||||
const notificationControl = await getNotificationProviderControl(control.ctx, control)
|
||||
const receivers = await getReceiversInfo(control.ctx, [...new Set(all.map((n) => n.user))], control)
|
||||
const receiverByAccount = new Map(receivers.map((r) => [r.account, r]))
|
||||
for (const n of all) {
|
||||
if (hasEmailProvider(n, availableProviders)) continue
|
||||
const type = (n.types ?? [])[0]
|
||||
if (type === undefined) {
|
||||
control.ctx.info('NotificationsHandler: skipping notification without type', { notificationId: n._id })
|
||||
continue
|
||||
}
|
||||
const notificationType = control.modelDb.getObject(type)
|
||||
const receiver = receiverByAccount.get(n.user)
|
||||
if (receiver === undefined) {
|
||||
control.ctx.info('NotificationsHandler: no receiver info for user', {
|
||||
notificationId: n._id,
|
||||
type,
|
||||
reason: 'user not in getReceiversInfo result'
|
||||
})
|
||||
continue
|
||||
}
|
||||
const allowedProviders = getAllowedProviders(control, receiver.socialIds, notificationType, notificationControl)
|
||||
if (allowedProviders.includes(gmail.providers.EmailNotificationProvider)) {
|
||||
notificationsWithEmail.push(n)
|
||||
} else {
|
||||
control.ctx.info('NotificationsHandler: email provider not enabled for notification type', {
|
||||
notificationId: n._id,
|
||||
type
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (notificationsWithEmail.length === 0) {
|
||||
control.ctx.info('NotificationsHandler: no notifications with email provider, skipping')
|
||||
return []
|
||||
}
|
||||
|
||||
const notificationsByDocId = groupByArray(all, (n) => n.objectId)
|
||||
const notificationsByDocId = groupByArray(notificationsWithEmail, (n) => n.objectId)
|
||||
|
||||
await Promise.all(
|
||||
Array.from(notificationsByDocId.entries()).map(([docId, notifications]) =>
|
||||
|
||||
@@ -183,13 +183,22 @@ export async function getContentByTemplate (
|
||||
const notificationType = control.modelDb.getObject(type)
|
||||
if (notificationType.templates === undefined) return
|
||||
|
||||
const textPart = await getTextPart(doc, control)
|
||||
if (textPart === undefined) return
|
||||
const params: Record<string, string> =
|
||||
notificationData !== undefined
|
||||
? await getTranslatedNotificationContent(notificationData, notificationData._class, control)
|
||||
: {}
|
||||
|
||||
let textPart = await getTextPart(doc, control)
|
||||
if (textPart === undefined) {
|
||||
if (
|
||||
notificationData !== undefined &&
|
||||
control.hierarchy.isDerived(notificationData._class, notification.class.CommonInboxNotification)
|
||||
) {
|
||||
textPart = params.title ?? params.body ?? ''
|
||||
}
|
||||
if (textPart === undefined || textPart === '') return
|
||||
}
|
||||
|
||||
if (
|
||||
notificationData !== undefined &&
|
||||
control.hierarchy.isDerived(notificationData._class, notification.class.MentionInboxNotification)
|
||||
|
||||
@@ -266,10 +266,13 @@ function getMatchedTypes (
|
||||
isSpace: boolean,
|
||||
field?: string
|
||||
): NotificationType[] {
|
||||
const allTypes = control.modelDb
|
||||
.findAllSync(notification.class.NotificationType, { ...(field !== undefined ? { field } : {}) })
|
||||
.filter((p) => (isSpace ? p.spaceSubscribe === true : p.spaceSubscribe !== true))
|
||||
const filtered: NotificationType[] = []
|
||||
let allTypes: NotificationType[] = control.modelDb.findAllSync(notification.class.NotificationType, {})
|
||||
allTypes = allTypes.filter(
|
||||
(p) =>
|
||||
(isSpace ? p.spaceSubscribe === true : p.spaceSubscribe !== true) &&
|
||||
(field === undefined || p.field === field || p.field === undefined)
|
||||
)
|
||||
for (const type of allTypes) {
|
||||
if (isTypeMatched(control, type, tx, isOwn)) {
|
||||
filtered.push(type)
|
||||
|
||||
@@ -127,6 +127,8 @@
|
||||
"@hcengineering/hr-assets": "workspace:^0.7.0",
|
||||
"@hcengineering/request-assets": "workspace:^0.7.0",
|
||||
"@hcengineering/document-assets": "workspace:^0.7.0",
|
||||
"@hcengineering/export": "workspace:^0.7.0",
|
||||
"@hcengineering/export-assets": "workspace:^0.7.0",
|
||||
"@hcengineering/controlled-documents-assets": "workspace:^0.7.0",
|
||||
"@hcengineering/products-assets": "workspace:^0.7.0",
|
||||
"@hcengineering/training-assets": "workspace:^0.7.0",
|
||||
|
||||
@@ -8,6 +8,7 @@ import { chunterId } from '@hcengineering/chunter'
|
||||
import { contactId } from '@hcengineering/contact'
|
||||
import { documentsId } from '@hcengineering/controlled-documents'
|
||||
import { documentId } from '@hcengineering/document'
|
||||
import { exportId } from '@hcengineering/export'
|
||||
import { driveId } from '@hcengineering/drive'
|
||||
import { githubId } from '@hcengineering/github'
|
||||
import { gmailId } from '@hcengineering/gmail'
|
||||
@@ -50,6 +51,7 @@ import chunterEn from '@hcengineering/chunter-assets/lang/en.json'
|
||||
import contactEn from '@hcengineering/contact-assets/lang/en.json'
|
||||
import documentsEn from '@hcengineering/controlled-documents-assets/lang/en.json'
|
||||
import documentEn from '@hcengineering/document-assets/lang/en.json'
|
||||
import exportEn from '@hcengineering/export-assets/lang/en.json'
|
||||
import driveEn from '@hcengineering/drive-assets/lang/en.json'
|
||||
import githubEn from '@hcengineering/github-assets/lang/en.json'
|
||||
import gmailEn from '@hcengineering/gmail-assets/lang/en.json'
|
||||
@@ -109,6 +111,7 @@ export function registerStringLoaders (): void {
|
||||
addStringsLoader(preferenceId, async (lang: string) => preferenceEn)
|
||||
addStringsLoader(hrId, async (lang: string) => hrEn)
|
||||
addStringsLoader(documentId, async (lang: string) => documentEn)
|
||||
addStringsLoader(exportId, async (lang: string) => exportEn)
|
||||
addStringsLoader(requestId, async (lang: string) => requestEn)
|
||||
addStringsLoader(loveId, async (lang: string) => loveEn)
|
||||
addStringsLoader(driveId, async (lang: string) => driveEn)
|
||||
|
||||
@@ -85,6 +85,7 @@ export async function sendExportCompletionNotification (
|
||||
objectId: resultId,
|
||||
objectClass: exportPlugin.class.ExportResultRecord,
|
||||
icon: exportPlugin.icon.Export,
|
||||
header: exportPlugin.string.ImportCompleted,
|
||||
message: exportPlugin.string.ImportToWorkspaceNotificationMessage,
|
||||
props: {
|
||||
count,
|
||||
|
||||
Reference in New Issue
Block a user