Configurable Back reference (#848)

Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
Andrey Sobolev
2022-01-21 10:05:55 +01:00
committed by GitHub
parent ea19fe2bbf
commit 5f5dc63d21
43 changed files with 786 additions and 238 deletions
+31 -8
View File
@@ -55,6 +55,7 @@ specifiers:
'@rush-temp/model-gmail': file:./projects/model-gmail.tgz
'@rush-temp/model-inventory': file:./projects/model-inventory.tgz
'@rush-temp/model-lead': file:./projects/model-lead.tgz
'@rush-temp/model-presentation': file:./projects/model-presentation.tgz
'@rush-temp/model-recruit': file:./projects/model-recruit.tgz
'@rush-temp/model-rig': file:./projects/model-rig.tgz
'@rush-temp/model-server-attachment': file:./projects/model-server-attachment.tgz
@@ -239,6 +240,7 @@ dependencies:
'@rush-temp/model-gmail': file:projects/model-gmail.tgz_typescript@4.5.4
'@rush-temp/model-inventory': file:projects/model-inventory.tgz_typescript@4.5.4
'@rush-temp/model-lead': file:projects/model-lead.tgz_typescript@4.5.4
'@rush-temp/model-presentation': file:projects/model-presentation.tgz_typescript@4.5.4
'@rush-temp/model-recruit': file:projects/model-recruit.tgz_typescript@4.5.4
'@rush-temp/model-rig': file:projects/model-rig.tgz_37f79b97d0d86442e45d380c86f520c5
'@rush-temp/model-server-attachment': file:projects/model-server-attachment.tgz_typescript@4.5.4
@@ -11634,7 +11636,7 @@ packages:
dev: false
file:projects/model-all.tgz_typescript@4.5.4:
resolution: {integrity: sha512-Z4F3zvl3i5iZUuhvKo1+SlVy9Urq09lVL4XsyY1zAwk14Af42uf5CVbbubPiSuLt4SevW1StJ5c08y4vztbfQA==, tarball: file:projects/model-all.tgz}
resolution: {integrity: sha512-xDbSAT10XpV9IMIWZmkZF1DKtDmPuh++4I5oxtxRHGrrdJ1h0J37nkAmZ6AayfWgqaCXG8EBn+SplvLw07HESg==, tarball: file:projects/model-all.tgz}
id: file:projects/model-all.tgz
name: '@rush-temp/model-all'
version: 0.0.0
@@ -11701,7 +11703,7 @@ packages:
dev: false
file:projects/model-contact.tgz_typescript@4.5.4:
resolution: {integrity: sha512-GD5NmMEaz2KD79NdbNlQTbmuc98hxeV0cwV8xgRtCZnbeNrDw4h71yUik4vGNLsgha8ezy2oefrdi5aET1wbig==, tarball: file:projects/model-contact.tgz}
resolution: {integrity: sha512-a2k6USkc9xrm3ba78A0pOps3X4Ox8A+uHm3cdNpfrSA8ihdfU9p6imeFr+ZotWF8YCcnwagz9sVHpT5dtmH4Mg==, tarball: file:projects/model-contact.tgz}
id: file:projects/model-contact.tgz
name: '@rush-temp/model-contact'
version: 0.0.0
@@ -11826,8 +11828,29 @@ packages:
- typescript
dev: false
file:projects/model-presentation.tgz_typescript@4.5.4:
resolution: {integrity: sha512-cBkfqfxIgaViuDI4bj0NDuVg1NnzoSjL2mxvRRRn1Dnqkq+2K00cKyx67Pil9dHwH636h2HEYKoM4VNsT3Os1w==, tarball: file:projects/model-presentation.tgz}
id: file:projects/model-presentation.tgz
name: '@rush-temp/model-presentation'
version: 0.0.0
dependencies:
'@rushstack/heft': 0.41.8
'@types/heft-jest': 1.0.2
'@typescript-eslint/eslint-plugin': 5.7.0_c25e8c1f4f4f7aaed27aa6f9ce042237
'@typescript-eslint/parser': 5.7.0_eslint@7.32.0+typescript@4.5.4
eslint: 7.32.0
eslint-config-standard-with-typescript: 21.0.1_ce2fa0c4dfa1c256100cababd749a13a
eslint-plugin-import: 2.25.3_eslint@7.32.0
eslint-plugin-node: 11.1.0_eslint@7.32.0
eslint-plugin-promise: 5.2.0_eslint@7.32.0
prettier: 2.5.1
transitivePeerDependencies:
- supports-color
- typescript
dev: false
file:projects/model-recruit.tgz_typescript@4.5.4:
resolution: {integrity: sha512-wIeXWWUD1Lwg8R43hEvmJzZ/ooN4DWdqv1+2vPFDZU/1xKwlW7Gqu1hZM5qUUVHbLLgMOmHsHDscPfn7tB11UQ==, tarball: file:projects/model-recruit.tgz}
resolution: {integrity: sha512-M2V190p42TMpwBagu8ts2iLvDu2hFprTiMX2i/KIwANipE9lJGHmjYlvh7HEGsdSYbeFavMuWdqoUNWjnCjy1Q==, tarball: file:projects/model-recruit.tgz}
id: file:projects/model-recruit.tgz
name: '@rush-temp/model-recruit'
version: 0.0.0
@@ -11951,7 +11974,7 @@ packages:
dev: false
file:projects/model-task.tgz_typescript@4.5.4:
resolution: {integrity: sha512-+R3a/p9KvLJRF7CCbFwZer5TPDbBAjpTW85JDRrpV4sL2bEQIgtgtoYYwtd8dMyqCld1MToYesR9WYvwHmVGFw==, tarball: file:projects/model-task.tgz}
resolution: {integrity: sha512-yIegyos933cu1NpvP/oFomGd8Yo9wKWjyoQyxNO26ERynPFdYIZ7yRKeiIpVp/P2T2xT3M9510GfdWJx6/MkgA==, tarball: file:projects/model-task.tgz}
id: file:projects/model-task.tgz
name: '@rush-temp/model-task'
version: 0.0.0
@@ -12229,7 +12252,7 @@ packages:
dev: false
file:projects/prod.tgz_sass@1.45.0+typescript@4.5.4:
resolution: {integrity: sha512-WxsSu4KtdtPTf1EJkHs/CJFDHiU0AgfGMmyOm66DFGc0cWOiJlgoCcw9TuCNaQPCBQ/ldbNYbfpj01N4iZFYuA==, tarball: file:projects/prod.tgz}
resolution: {integrity: sha512-FWLkhyyIA2ZiXuydQap4fYlvfq02Q18A0fjVgVmy/7Wedebed2Xj6n2uQVjDVN50a5EP+5CutRcLAuxs7bI4Vw==, tarball: file:projects/prod.tgz}
id: file:projects/prod.tgz
name: '@rush-temp/prod'
version: 0.0.0
@@ -12495,7 +12518,7 @@ packages:
dev: false
file:projects/server.tgz:
resolution: {integrity: sha512-e8Oeh7z0NrGebPnTPZDsh4H6UUwDv2F4ywRw2o6GW7r3iXd1iK4nbxqs1bG7DvHy7kpY+jWk6tMpFSYJ+RXOrg==, tarball: file:projects/server.tgz}
resolution: {integrity: sha512-5U67EYHkOoUqolvKMKSN1++52yfIzKPOIG2pmnbra2Ffcg9O/7UCat9eBIUx8U+n1998DnVm+c9H3yHadlOdsw==, tarball: file:projects/server.tgz}
name: '@rush-temp/server'
version: 0.0.0
dependencies:
@@ -12723,7 +12746,7 @@ packages:
dev: false
file:projects/text-editor.tgz_096c09b0b673a57c275d9767a12070b1:
resolution: {integrity: sha512-Fg9TwM9Q/v+2QLbOwTuBAhjI0/4Lm34ksUW2R0PjuY0WTUGk/ZbKALm2HJAr+fQgjd8B1FXjcFXlidoOIWQ9jA==, tarball: file:projects/text-editor.tgz}
resolution: {integrity: sha512-mHTzsgBUTd2d3Y8xW1y8GOTLU0r4IyOsF7RpV1N4GVwaqnCZSxuFdC/T7PqX33XYI0hD6q+0KNegRs+MzvOeeg==, tarball: file:projects/text-editor.tgz}
id: file:projects/text-editor.tgz
name: '@rush-temp/text-editor'
version: 0.0.0
@@ -12805,7 +12828,7 @@ packages:
dev: false
file:projects/tool.tgz:
resolution: {integrity: sha512-Zwy6rf7v5DtpINp3IaWvHYoEVdkaQMi2n837scjAUyUjp21wMQshZudzu/K00Nfi4vI/Z40Ue8hG6LrcrUHQzA==, tarball: file:projects/tool.tgz}
resolution: {integrity: sha512-dfzDNJuguQfZyQh/Z9IkPneooJEK0O5kQcEnqOyPiJIvv/HPK2dNw2U97BrWhjylNqTMxDziWY2zkMXiUdEkTg==, tarball: file:projects/tool.tgz}
name: '@rush-temp/tool'
version: 0.0.0
dependencies:
+1
View File
@@ -47,6 +47,7 @@
"@anticrm/model-attachment": "~0.6.0",
"@anticrm/model-gmail": "~0.6.0",
"@anticrm/model-inventory": "~0.6.0",
"@anticrm/model-presentation": "~0.6.0",
"@anticrm/core": "~0.6.13"
}
}
+2
View File
@@ -28,6 +28,7 @@ import { createModel as attachmentModel } from '@anticrm/model-attachment'
import { createModel as leadModel } from '@anticrm/model-lead'
import { createModel as gmailModel } from '@anticrm/model-gmail'
import { createModel as inventoryModel } from '@anticrm/model-inventory'
import { createModel as presentationModel } from '@anticrm/model-presentation'
import { createModel as serverCoreModel } from '@anticrm/model-server-core'
import { createModel as serverAttachmentModel } from '@anticrm/model-server-attachment'
@@ -52,6 +53,7 @@ telegramModel(builder)
leadModel(builder)
gmailModel(builder)
inventoryModel(builder)
presentationModel(builder)
serverCoreModel(builder)
serverAttachmentModel(builder)
+1 -1
View File
@@ -143,7 +143,7 @@ export function createModel (builder: Builder): void {
component: chunter.activity.TxBacklinkCreate,
label: chunter.string.MentionedIn,
labelComponent: chunter.activity.TxBacklinkReference,
display: 'content',
display: 'emphasized',
editable: false,
hideOnRemove: true
}, chunter.ids.TxCommentCreate)
+1
View File
@@ -30,6 +30,7 @@
"@anticrm/model-workbench": "~0.6.0",
"@anticrm/model-attachment": "~0.6.0",
"@anticrm/model-view": "~0.6.0",
"@anticrm/model-presentation": "~0.6.0",
"@anticrm/model": "~0.6.0",
"@anticrm/core": "~0.6.11",
"@anticrm/ui": "~0.6.0",
+19
View File
@@ -34,6 +34,7 @@ import workbench from '@anticrm/model-workbench'
import view from '@anticrm/model-view'
import attachment from '@anticrm/model-attachment'
import { ids as contact } from './plugin'
import presentation from '@anticrm/model-presentation'
export const DOMAIN_CONTACT = 'contact' as Domain
@@ -234,6 +235,24 @@ export function createModel (builder: Builder): void {
builder.mixin(contact.class.Contact, core.class.Class, view.mixin.AttributePresenter, {
presenter: contact.component.ContactPresenter
})
builder.createDoc(presentation.class.ObjectSearchCategory, core.space.Model, {
icon: contact.icon.Person,
label: contact.string.SearchEmployee,
query: contact.completion.EmployeeQuery
}, contact.completion.EmployeeCategory)
builder.createDoc(presentation.class.ObjectSearchCategory, core.space.Model, {
icon: contact.icon.Person,
label: contact.string.SearchPerson,
query: contact.completion.PersonQuery
}, contact.completion.PersonCategory)
builder.createDoc(presentation.class.ObjectSearchCategory, core.space.Model, {
icon: contact.icon.Company,
label: contact.string.SearchOrganization,
query: contact.completion.OrganizationQuery
}, contact.completion.OrganizationCategory)
}
export { contact as default }
+14 -2
View File
@@ -13,13 +13,14 @@
// limitations under the License.
//
import { IntlString, mergeIds } from '@anticrm/platform'
import { IntlString, mergeIds, Resource } from '@anticrm/platform'
import type { Ref, Class, Type } from '@anticrm/core'
import contact, { contactId } from '@anticrm/contact'
import type { Channel } from '@anticrm/contact'
import type { AnyComponent } from '@anticrm/ui'
import {} from '@anticrm/core'
import { Application } from '@anticrm/workbench'
import { ObjectSearchCategory, ObjectSearchFactory } from '@anticrm/model-presentation'
export const ids = mergeIds(contactId, contact, {
app: {
@@ -45,9 +46,20 @@ export const ids = mergeIds(contactId, contact, {
Persons: '' as IntlString,
Contacts: '' as IntlString,
CreatePersons: '' as IntlString,
CreateOrganizations: '' as IntlString
CreateOrganizations: '' as IntlString,
SearchEmployee: '' as IntlString,
SearchPerson: '' as IntlString,
SearchOrganization: '' as IntlString
},
class: {
TypeChannel: '' as Ref<Class<Type<Channel>>>
},
completion: {
PersonQuery: '' as Resource<ObjectSearchFactory>,
EmployeeQuery: '' as Resource<ObjectSearchFactory>,
OrganizationQuery: '' as Resource<ObjectSearchFactory>,
EmployeeCategory: '' as Ref<ObjectSearchCategory>,
PersonCategory: '' as Ref<ObjectSearchCategory>,
OrganizationCategory: '' as Ref<ObjectSearchCategory>
}
})
+7
View File
@@ -0,0 +1,7 @@
module.exports = {
extends: ['./node_modules/@anticrm/model-rig/profiles/default/config/eslint.config.json'],
parserOptions: {
tsconfigRootDir: __dirname,
project: './tsconfig.json'
}
}
+4
View File
@@ -0,0 +1,4 @@
*
!/lib/**
!CHANGELOG.md
/lib/**/__tests__/
+18
View File
@@ -0,0 +1,18 @@
// The "rig.json" file directs tools to look for their config files in an external package.
// Documentation for this system: https://www.npmjs.com/package/@rushstack/rig-package
{
"$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json",
/**
* (Required) The name of the rig package to inherit from.
* It should be an NPM package name with the "-rig" suffix.
*/
"rigPackageName": "@anticrm/model-rig"
/**
* (Optional) Selects a config profile from the rig package. The name must consist of
* lowercase alphanumeric words separated by hyphens, for example "sample-profile".
* If omitted, then the "default" profile will be used."
*/
// "rigProfile": "your-profile-name"
}
+35
View File
@@ -0,0 +1,35 @@
{
"name": "@anticrm/model-presentation",
"version": "0.6.0",
"main": "lib/index.js",
"author": "Anticrm Platform Contributors",
"license": "EPL-2.0",
"scripts": {
"build": "heft build",
"build:watch": "tsc",
"lint:fix": "eslint --fix src",
"lint": "eslint src",
"format": "prettier --write src && eslint --fix src"
},
"devDependencies": {
"@anticrm/model-rig": "~0.6.0",
"@typescript-eslint/eslint-plugin": "^5.4.0",
"eslint-plugin-import": "^2.25.3",
"eslint-plugin-promise": "^5.1.1",
"eslint-plugin-node": "^11.1.0",
"eslint": "^7.32.0",
"@types/heft-jest": "^1.0.2",
"@typescript-eslint/parser": "^5.4.0",
"eslint-config-standard-with-typescript": "^21.0.1",
"prettier": "^2.4.1",
"@rushstack/heft": "^0.41.1"
},
"dependencies": {
"@anticrm/core": "~0.6.11",
"@anticrm/model": "~0.6.0",
"@anticrm/platform": "~0.6.5",
"@anticrm/ui": "~0.6.0",
"@anticrm/presentation": "~0.6.0",
"@anticrm/model-core": "~0.6.0"
}
}
+40
View File
@@ -0,0 +1,40 @@
//
// Copyright © 2020 Anticrm Platform Contributors.
//
// 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 { DOMAIN_MODEL } from '@anticrm/core'
import { Builder, Model } from '@anticrm/model'
import core, { TDoc } from '@anticrm/model-core'
import type { Asset, IntlString, Resource } from '@anticrm/platform'
// Import types to prevent .svelte components to being exposed to type typescript.
import { ObjectSearchCategory, ObjectSearchFactory } from '@anticrm/presentation/src/types'
import presentation from './plugin'
export { default } from './plugin'
export { ObjectSearchCategory, ObjectSearchFactory }
@Model(presentation.class.ObjectSearchCategory, core.class.Doc, DOMAIN_MODEL)
export class TObjectSearchCategory extends TDoc implements ObjectSearchCategory {
label!: IntlString
icon!: Asset
// Query for documents with pattern
query!: Resource<ObjectSearchFactory>
}
export function createModel (builder: Builder): void {
builder.createModel(
TObjectSearchCategory
)
}
+23
View File
@@ -0,0 +1,23 @@
//
// Copyright © 2020, 2021 Anticrm Platform Contributors.
// Copyright © 2021 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 { mergeIds } from '@anticrm/platform'
// Import plugin directly to prevent .svelte components to being exposed to type typescript.
import presentation, { presentationId } from '@anticrm/presentation/src/plugin'
export default mergeIds(presentationId, presentation, {
})
+8
View File
@@ -0,0 +1,8 @@
{
"extends": "./node_modules/@anticrm/model-rig/profiles/default/tsconfig.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./lib",
}
}
+2 -1
View File
@@ -42,6 +42,7 @@
"@anticrm/view": "~0.6.0",
"@anticrm/task": "~0.6.0",
"@anticrm/model-task": "~0.6.0",
"@anticrm/workbench": "~0.6.1"
"@anticrm/workbench": "~0.6.1",
"@anticrm/model-presentation": "~0.6.0"
}
}
+7
View File
@@ -26,6 +26,7 @@ import workbench from '@anticrm/model-workbench'
import type { IntlString } from '@anticrm/platform'
import { Applicant, Candidate, Candidates, Vacancy } from '@anticrm/recruit'
import recruit from './plugin'
import presentation from '@anticrm/model-presentation'
@Model(recruit.class.Vacancy, task.class.SpaceWithStates)
@UX(recruit.string.Vacancy, recruit.icon.Vacancy)
@@ -281,6 +282,12 @@ export function createModel (builder: Builder): void {
},
recruit.space.VacancyTemplates
)
builder.createDoc(presentation.class.ObjectSearchCategory, core.space.Model, {
icon: recruit.icon.Application,
label: recruit.string.SearchApplication,
query: recruit.completion.ApplicationQuery
}, recruit.completion.ApplicationCategory)
}
export { default } from './plugin'
+7 -1
View File
@@ -22,6 +22,7 @@ import { KanbanTemplate } from '@anticrm/task'
import type { AnyComponent } from '@anticrm/ui'
import type { Action } from '@anticrm/view'
import { Application } from '@anticrm/workbench'
import { ObjectSearchFactory, ObjectSearchCategory } from '@anticrm/model-presentation'
export default mergeIds(recruitId, recruit, {
app: {
@@ -36,7 +37,8 @@ export default mergeIds(recruitId, recruit, {
string: {
RecruitApplication: '' as IntlString,
Vacancies: '' as IntlString,
CandidatePools: '' as IntlString
CandidatePools: '' as IntlString,
SearchApplication: '' as IntlString
},
validator: {
ApplicantValidator: '' as Resource<<T extends Doc>(doc: T, client: Client) => Promise<Status>>
@@ -56,5 +58,9 @@ export default mergeIds(recruitId, recruit, {
},
template: {
DefaultVacancy: '' as Ref<KanbanTemplate>
},
completion: {
ApplicationQuery: '' as Resource<ObjectSearchFactory>,
ApplicationCategory: '' as Ref<ObjectSearchCategory>
}
})
+2 -1
View File
@@ -39,6 +39,7 @@
"@anticrm/task-resources": "~0.6.0",
"@anticrm/model-chunter": "~0.6.0",
"@anticrm/workbench": "~0.6.1",
"@anticrm/view": "~0.6.0"
"@anticrm/view": "~0.6.0",
"@anticrm/model-presentation": "~0.6.0"
}
}
+17 -29
View File
@@ -14,53 +14,38 @@
//
// To help typescript locate view plugin properly
import type { ActionTarget } from '@anticrm/view'
import attachment from '@anticrm/model-attachment'
import type { Employee } from '@anticrm/contact'
import contact from '@anticrm/contact'
import { Arr, Class, Doc, Domain, DOMAIN_MODEL, FindOptions, Ref, Space, Timestamp } from '@anticrm/core'
import {
Builder,
Collection,
Implements,
Collection, Hidden, Implements,
Mixin,
Model,
Prop,
Hidden,
TypeBoolean,
Prop, TypeBoolean,
TypeDate,
TypeRef,
TypeString,
UX
} from '@anticrm/model'
import attachment from '@anticrm/model-attachment'
import chunter from '@anticrm/model-chunter'
import core, { TAttachedDoc, TClass, TDoc, TSpace } from '@anticrm/model-core'
import presentation from '@anticrm/model-presentation'
import view from '@anticrm/model-view'
import workbench from '@anticrm/model-workbench'
import type { IntlString } from '@anticrm/platform'
import type {
Kanban,
KanbanCard,
Project,
State,
Issue,
Sequence,
DoneState,
WonState,
LostState,
KanbanTemplateSpace,
StateTemplate,
DoneStateTemplate,
WonStateTemplate,
LostStateTemplate,
KanbanTemplate,
Task,
TodoItem
DoneState, DoneStateTemplate, Issue, Kanban,
KanbanCard, KanbanTemplate, KanbanTemplateSpace, LostState, LostStateTemplate, Project, Sequence, State, StateTemplate, Task,
TodoItem, WonState, WonStateTemplate
} from '@anticrm/task'
import task from './plugin'
import { AnyComponent } from '@anticrm/ui'
import type { ActionTarget } from '@anticrm/view'
import task from './plugin'
export { createDeps, createKanbanTemplate } from './creation'
export { taskOperation } from './migration'
export { default } from './plugin'
export const DOMAIN_TASK = 'task' as Domain
@@ -519,7 +504,10 @@ export function createModel (builder: Builder): void {
done: true
}
})
}
export { taskOperation } from './migration'
export { createDeps, createKanbanTemplate } from './creation'
builder.createDoc(presentation.class.ObjectSearchCategory, core.space.Model, {
icon: task.icon.Task,
label: task.string.SearchTask,
query: task.completion.IssueQuery
}, task.completion.IssueCategory)
}
+7 -1
View File
@@ -22,6 +22,7 @@ import task from '@anticrm/task-resources/src/plugin'
import type { AnyComponent } from '@anticrm/ui'
import { Application } from '@anticrm/workbench'
import type { Action } from '@anticrm/view'
import { ObjectSearchCategory, ObjectSearchFactory } from '@anticrm/model-presentation'
export default mergeIds(taskId, task, {
app: {
@@ -85,12 +86,17 @@ export default mergeIds(taskId, task, {
MarkAsUndone: '' as IntlString,
Kanban: '' as IntlString,
ApplicationLabelTask: '' as IntlString,
Projects: '' as IntlString
Projects: '' as IntlString,
SearchTask: '' as IntlString
},
space: {
TasksPublic: '' as Ref<Space>
},
template: {
DefaultProject: '' as Ref<KanbanTemplate>
},
completion: {
IssueQuery: '' as Resource<ObjectSearchFactory>,
IssueCategory: '' as Ref<ObjectSearchCategory>
}
})
+2 -1
View File
@@ -16,9 +16,10 @@
import { addStringsLoader } from '@anticrm/platform'
import { presentationId } from './plugin'
export { presentationId }
export * from './utils'
export * from './attributes'
export * from './types'
export { default as UserBox } from './components/UserBox.svelte'
export { default as UserInfo } from './components/UserInfo.svelte'
+9 -4
View File
@@ -1,21 +1,23 @@
//
// Copyright © 2020, 2021 Anticrm Platform Contributors.
// Copyright © 2021 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 { Class, Ref } from '@anticrm/core'
import type { IntlString, Plugin } from '@anticrm/platform'
import { plugin } from '@anticrm/platform'
import { ObjectSearchCategory } from '.'
/**
* @public
@@ -23,6 +25,9 @@ import { plugin } from '@anticrm/platform'
export const presentationId = 'presentation' as Plugin
export default plugin(presentationId, {
class: {
ObjectSearchCategory: '' as Ref<Class<ObjectSearchCategory>>
},
string: {
Create: '' as IntlString,
Cancel: '' as IntlString,
@@ -34,4 +39,4 @@ export default plugin(presentationId, {
Deselect: '' as IntlString,
AddSocialLinks: '' as IntlString
}
})
})
+31
View File
@@ -0,0 +1,31 @@
import { Client, Doc } from '@anticrm/core'
import { Asset, IntlString, Resource } from '@anticrm/platform'
import { AnySvelteComponent } from '@anticrm/ui'
/**
* @public
*/
export interface ObjectSearchResult {
doc: Doc
component?: AnySvelteComponent
componentProps?: any
title: string
icon?: Asset | AnySvelteComponent
iconProps?: any
}
/**
* @public
*/
export type ObjectSearchFactory = (client: Client, query: string) => Promise<ObjectSearchResult[]>
/**
* @public
*/
export interface ObjectSearchCategory extends Doc {
label: IntlString
icon: Asset
// Query for documents with pattern
query: Resource<ObjectSearchFactory>
}
+5
View File
@@ -0,0 +1,5 @@
{
"string": {
"Suggested": "SUGGESTED"
}
}
+1
View File
@@ -34,6 +34,7 @@
"svelte": "^3.37.0",
"@anticrm/presentation": "~0.6.2",
"@anticrm/contact": "~0.6.2",
"@anticrm/platform": "~0.6.5",
"@anticrm/core": "~0.6.11",
"@anticrm/ui": "~0.6.0",
"@tiptap/core": "~2.0.0-beta.93",
@@ -14,19 +14,29 @@
// limitations under the License.
-->
<script lang="ts">
import { formatName, Person } from '@anticrm/contact'
import { UserInfo } from '@anticrm/presentation'
import { createEventDispatcher } from 'svelte';
export let items: Person[]
export let query: string
import { getResource, translate } from '@anticrm/platform'
import { getClient, ObjectSearchCategory, ObjectSearchResult } from '@anticrm/presentation'
import { ActionIcon, EditWithIcon, IconSearch, Label } from '@anticrm/ui'
import plugin from '../plugin'
export let query: string = ''
export let items: ObjectSearchResult[]
export let clientRect: () => ClientRect
export let command: (props: any) => void
export let close: () => void
export let categories: ObjectSearchCategory[]
export let category: ObjectSearchCategory
export let onCategory: (cat: ObjectSearchCategory) => void
const client = getClient()
let popup: HTMLDivElement
let selected = 0
function dispatchItem (item: ObjectSearchResult): void {
command({ id: item.doc._id, label: item.title, objectclass: item.doc._class })
}
export function onKeyDown (ev: KeyboardEvent) {
if (ev.key === 'ArrowDown') {
if (selected < items.length - 1) selected++
@@ -36,19 +46,24 @@ import { createEventDispatcher } from 'svelte';
if (selected > 0) selected--
return true
}
if (ev.key === 'Tab') {
const pos = categories.indexOf(category)
category = categories[(pos + 1) % categories.length]
return true
}
if (ev.key === 'Enter') {
const person = items[selected]
if (person) {
command({ id: person._id, label: formatName(person.name), objectclass: person._class })
const item = items[selected]
if (item) {
dispatchItem(item)
return true
} else {
return false
}
}
// TODO: How to prevent Esc, it should hide popup instead of closing editor.
// if (ev.key === 'Esc') {
// return true
// }
if (ev.key === 'Esc') {
return false
}
return false
}
@@ -56,15 +71,32 @@ import { createEventDispatcher } from 'svelte';
console.log('done')
}
function updateStyle (popup: HTMLDivElement): void {
const x = clientRect().left
const height = popup.getBoundingClientRect().height
const y = clientRect().top - height - 16
style = `left: ${x}px; top: ${y}px;`
}
let style = 'visibility: hidden'
$: {
if (popup) {
const x = clientRect().left
const height = popup.getBoundingClientRect().height
const y = clientRect().top - height - 16
style = `left: ${x}px; top: ${y}px;`
$: if (popup) {
updateStyle(popup)
}
async function updateItems (category: ObjectSearchCategory, query: string): Promise<void> {
const f = await getResource(category.query)
items = await f(client, query)
if (selected > items.length) {
selected = 0
}
}
$: updateItems(category, query)
let placeholder = ''
$: translate(category.label, {}).then((v) => {
placeholder = v
})
</script>
<div
@@ -74,15 +106,29 @@ import { createEventDispatcher } from 'svelte';
}}
/>
<div>
<div bind:this={popup} class="completion" {style}>
<div class="caption">Contacts</div>
<div class="scroll">
<div bind:this={popup} class="completion" {style} on:keydown={onKeyDown}>
<div class='category-container'>
{#each categories as c}
<div class='category-selector' class:selected={category.label === c.label}>
<ActionIcon label={c.label} icon={c.icon} size={'medium'} action={() => {
category = c
onCategory(c)
updateItems(c, query)
} }/>
</div>
{/each}
</div>
<div class='mt-4 mb-4'>
<EditWithIcon icon={IconSearch} bind:value={query} on:input={() => updateItems(category, query) } placeholder={placeholder} />
</div>
<Label label={plugin.string.Suggested}/>
<div class="scroll mt-2">
{#each items as item, i}
<div
class="item"
class:selected={i === selected}
on:click={() => {
command({ id: item._id, label: formatName(item.name), objectclass: item._class })
dispatchItem(item)
}}
on:focus={() => {
selected = i
@@ -91,7 +137,7 @@ import { createEventDispatcher } from 'svelte';
selected = i
}}
>
<UserInfo size={'medium'} value={item} />
<svelte:component this={item.component} value={item.doc} {...item.componentProps ?? {}} />
</div>
{/each}
</div>
@@ -115,6 +161,8 @@ import { createEventDispatcher } from 'svelte';
.completion {
position: absolute;
z-index: 2010;
min-width: 300px;
height: 332px;
padding: 16px;
background-color: var(--theme-button-bg-hovered);
border: 1px solid var(--theme-bg-accent-hover);
@@ -129,12 +177,22 @@ import { createEventDispatcher } from 'svelte';
color: var(--theme-content-trans-color);
}
.scroll {
max-height: 200px;
max-height: calc(300px - 128px);
display: grid;
grid-auto-flow: row;
gap: 12px;
height: calc(100% - 71px);
overflow-y: auto;
}
}
.category-container {
display: flex;
.category-selector {
margin-right: 1rem;
opacity: 0.7;
&.selected {
opacity: 1;
}
}
}
</style>
@@ -12,26 +12,89 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import { getResource } from '@anticrm/platform'
import presentation, { getClient, ObjectSearchCategory, ObjectSearchFactory } from '@anticrm/presentation'
import { AnyExtension } from '@tiptap/core'
import { createEventDispatcher } from 'svelte'
import Send from './icons/Send.svelte'
import { Completion } from '../Completion'
import Attach from './icons/Attach.svelte'
import Emoji from './icons/Emoji.svelte'
import GIF from './icons/GIF.svelte'
import Send from './icons/Send.svelte'
import TextStyle from './icons/TextStyle.svelte'
import MentionList from './MentionList.svelte'
import { SvelteRenderer } from './SvelteRenderer'
import TextEditor from './TextEditor.svelte'
const dispatch = createEventDispatcher()
export let content: string = ''
export let showSend = true
const client = getClient()
let textEditor: TextEditor
export let categories: ObjectSearchCategory[] = []
client.findAll(presentation.class.ObjectSearchCategory, {}).then((r) => {
categories = r
})
// Current selected category
let category: ObjectSearchCategory | undefined = categories[0]
$: if (categories.length > 0 && category === undefined) {
category = categories[0]
}
export function submit (): void {
textEditor.submit()
}
const editorExtensions: AnyExtension[] = [
Completion.configure({
HTMLAttributes: {
class: 'reference'
},
suggestion: {
items: async (query: { query: string }) => {
if (category !== undefined) {
const f = await getResource(category.query)
return (await f(client, query.query))
}
return []
},
render: () => {
let component: any
return {
onStart: (props:any) => {
component = new SvelteRenderer(MentionList, {
...props,
close: () => { component.destroy() },
categories,
category,
onCategory: (cat: ObjectSearchCategory) => {
category = cat
}
})
},
onUpdate (props:any) {
component.updateProps(props)
},
onKeyDown (props:any) {
return component.onKeyDown(props)
},
onExit () {
component.destroy()
}
}
}
}
})
]
</script>
<div class="ref-container">
@@ -43,7 +106,7 @@
content = ''
textEditor.clear()
}
}/>
} extensions={editorExtensions} />
</div>
{#if showSend}
<button class="sendButton" on:click={submit}><div class="icon"><Send/></div></button>
@@ -15,122 +15,84 @@
-->
<script lang="ts">
import { AnyExtension, Editor, Extension } from '@tiptap/core'
import Highlight from '@tiptap/extension-highlight'
import Link from '@tiptap/extension-link'
// import Typography from '@tiptap/extension-typography'
import Placeholder from '@tiptap/extension-placeholder'
import StarterKit from '@tiptap/starter-kit'
import { createEventDispatcher, onDestroy, onMount } from 'svelte'
import { onMount, onDestroy, createEventDispatcher } from 'svelte'
import { Editor, Extension } from '@tiptap/core'
import StarterKit from '@tiptap/starter-kit'
import Highlight from '@tiptap/extension-highlight'
import Link from '@tiptap/extension-link'
// import Typography from '@tiptap/extension-typography'
import Placeholder from '@tiptap/extension-placeholder'
// import Mention from '@tiptap/extension-mention'
import { Completion } from '../Completion'
export let content: string = ''
export let placeholder: string = 'Type something...'
export let extensions: AnyExtension[] = []
import MentionList from './MentionList.svelte'
import { SvelteRenderer } from './SvelteRenderer'
let element: HTMLElement
let editor: Editor
import { getClient } from '@anticrm/presentation'
const dispatch = createEventDispatcher()
import contact from '@anticrm/contact'
export function submit (): void {
content = editor.getHTML()
dispatch('content', content)
}
export let content: string = ''
export let placeholder: string = 'Type something...'
export function clear (): void {
content = ''
editor.commands.clearContent(false)
}
let element: HTMLElement
let editor: Editor
const dispatch = createEventDispatcher()
const client = getClient()
export function submit (): void {
content = editor.getHTML()
dispatch('content', content)
}
export function clear (): void {
content = ''
editor.commands.clearContent(false)
}
const Handle = Extension.create({
addKeyboardShortcuts () {
return {
'Shift-Enter': () => {
const res = this.editor.commands.splitListItem('listItem')
if (!res) {
this.editor.commands.first(({ commands }) => [
() => commands.newlineInCode(),
() => commands.createParagraphNear(),
() => commands.liftEmptyBlock(),
() => commands.splitBlock()
])
const Handle = Extension.create({
addKeyboardShortcuts () {
return {
'Shift-Enter': () => {
const res = this.editor.commands.splitListItem('listItem')
if (!res) {
this.editor.commands.first(({ commands }) => [
() => commands.newlineInCode(),
() => commands.createParagraphNear(),
() => commands.liftEmptyBlock(),
() => commands.splitBlock()
])
}
return true
},
Enter: () => {
submit()
return true
}
return true
},
Enter: () => {
submit()
return true
}
}
}
})
})
onMount(() => {
editor = new Editor({
element,
content: content,
extensions: [
StarterKit,
Highlight,
Link,
Handle, // order important
// Typography, // we need to disable 1/2 -> ½ rule (https://github.com/hcengineering/anticrm/issues/345)
Placeholder.configure({ placeholder: placeholder }),
Completion.configure({
HTMLAttributes: {
class: 'reference'
},
suggestion: {
items: async (query: { query: string }) => {
const persons = await client.findAll(contact.class.Person, { name: { $like: `%${query.query}%` } }, { limit: 200 })
return persons.filter(person => person.name.includes(query.query))
},
render: () => {
let component: any
onMount(() => {
editor = new Editor({
element,
content: content,
extensions: [
StarterKit,
Highlight,
Link,
Handle, // order important
// Typography, // we need to disable 1/2 -> ½ rule (https://github.com/hcengineering/anticrm/issues/345)
Placeholder.configure({ placeholder: placeholder }),
...extensions
],
onTransaction: () => {
// force re-render so `editor.isActive` works as expected
editor = editor
},
onBlur: () => {
dispatch('blur')
}
})
})
return {
onStart: (props:any) => {
component = new SvelteRenderer(MentionList, { ...props, close: () => { component.destroy() } })
},
onUpdate (props:any) {
component.updateProps(props)
},
onKeyDown (props:any) {
return component.onKeyDown(props)
},
onExit () {
component.destroy()
}
}
}
}
})
],
onTransaction: () => {
// force re-render so `editor.isActive` works as expected
editor = editor
},
onBlur: () => {
dispatch('blur')
onDestroy(() => {
if (editor) {
editor.destroy()
}
})
})
onDestroy(() => {
if (editor) {
editor.destroy()
}
})
</script>
<div style="width: 100%" bind:this={element}/>
+12 -16
View File
@@ -1,18 +1,14 @@
//
// Copyright © 2020, 2021 Anticrm Platform Contributors.
// Copyright © 2021 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 { addStringsLoader } from '@anticrm/platform'
import { textEditorId } from './plugin'
export { default as TextEditor } from './components/TextEditor.svelte'
//
export { default as ReferenceInput } from './components/ReferenceInput.svelte'
export { default as TextEditor } from './components/TextEditor.svelte'
export * from '@anticrm/presentation/src/types'
addStringsLoader(textEditorId, async (lang: string) => {
return await import(`../lang/${lang}.json`)
})
export { default } from './plugin'
+28
View File
@@ -0,0 +1,28 @@
//
// Copyright © 2020, 2021 Anticrm Platform Contributors.
// Copyright © 2021 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 { IntlString, Plugin, plugin } from '@anticrm/platform'
/**
* @public
*/
export const textEditorId = 'text-editor' as Plugin
export default plugin(textEditorId, {
string: {
Suggested: '' as IntlString
}
})
@@ -18,8 +18,7 @@
import type { TxViewlet } from '@anticrm/activity'
import activity from '@anticrm/activity'
import contact, { EmployeeAccount, formatName } from '@anticrm/contact'
import { AttachedDoc, Doc, Ref, TxCreateDoc } from '@anticrm/core'
import core from '@anticrm/core'
import { Doc, Ref } from '@anticrm/core'
import { Asset, getResource } from '@anticrm/platform'
import { getClient } from '@anticrm/presentation'
import {
+4 -1
View File
@@ -20,6 +20,9 @@
"PersonsFolder": "Persons folder",
"MakePrivate": "Make private",
"MakePrivateDescription": "Only members can see it",
"Create": "Contact"
"Create": "Contact",
"SearchEmployee": "Search for employee...",
"SearchPerson": "Search for person...",
"SearchOrganization": "Search for organization..."
}
}
+32 -12
View File
@@ -14,22 +14,37 @@
// limitations under the License.
//
import PersonPresenter from './components/PersonPresenter.svelte'
import ContactPresenter from './components/ContactPresenter.svelte'
import OrganizationPresenter from './components/OrganizationPresenter.svelte'
import ChannelsPresenter from './components/ChannelsPresenter.svelte'
import CreatePerson from './components/CreatePerson.svelte'
import CreateOrganization from './components/CreateOrganization.svelte'
import EditPerson from './components/EditPerson.svelte'
import EditOrganization from './components/EditOrganization.svelte'
import CreatePersons from './components/CreatePersons.svelte'
import CreateOrganizations from './components/CreateOrganizations.svelte'
import SocialEditor from './components/SocialEditor.svelte'
import Contacts from './components/Contacts.svelte'
import { Contact, formatName } from '@anticrm/contact'
import { Class, Client, Ref } from '@anticrm/core'
import { Resources } from '@anticrm/platform'
import { Avatar, ObjectSearchResult, UserInfo } from '@anticrm/presentation'
import ChannelsPresenter from './components/ChannelsPresenter.svelte'
import ContactPresenter from './components/ContactPresenter.svelte'
import Contacts from './components/Contacts.svelte'
import CreateOrganization from './components/CreateOrganization.svelte'
import CreateOrganizations from './components/CreateOrganizations.svelte'
import CreatePerson from './components/CreatePerson.svelte'
import CreatePersons from './components/CreatePersons.svelte'
import EditOrganization from './components/EditOrganization.svelte'
import EditPerson from './components/EditPerson.svelte'
import OrganizationPresenter from './components/OrganizationPresenter.svelte'
import PersonPresenter from './components/PersonPresenter.svelte'
import SocialEditor from './components/SocialEditor.svelte'
import contact from './plugin'
export { ContactPresenter }
async function queryContact (_class: Ref<Class<Contact>>, client: Client, search: string): Promise<ObjectSearchResult[]> {
return (await client.findAll(_class, { name: { $like: `%${search}%` } }, { limit: 200 })).map(e => ({
doc: e,
title: formatName(e.name),
icon: Avatar,
iconProps: { size: 'x-small', avatar: e.avatar },
component: UserInfo,
componentProps: { size: 'x-small' }
}))
}
export default async (): Promise<Resources> => ({
component: {
ContactPresenter,
@@ -44,5 +59,10 @@ export default async (): Promise<Resources> => ({
CreateOrganizations,
SocialEditor,
Contacts
},
completion: {
EmployeeQuery: async (client: Client, query: string) => await queryContact(contact.class.Employee, client, query),
PersonQuery: async (client: Client, query: string) => await queryContact(contact.class.Person, client, query),
OrganizationQuery: async (client: Client, query: string) => await queryContact(contact.class.Organization, client, query)
}
})
+1 -2
View File
@@ -14,9 +14,8 @@
// limitations under the License.
//
import { IntlString, mergeIds } from '@anticrm/platform'
import contact, { contactId } from '@anticrm/contact'
import { IntlString, mergeIds } from '@anticrm/platform'
export default mergeIds(contactId, contact, {
string: {
+2 -1
View File
@@ -30,7 +30,8 @@
"Delete": "Delete",
"WorkLocationPreferences": "Work location preferences",
"Onsite": "Onsite",
"Remote": "Remote"
"Remote": "Remote",
"SearchApplication": "Search for application..."
},
"status": {
"CandidateRequired": "Please select candidate",
@@ -0,0 +1,49 @@
<!--
// Copyright © 2020, 2021 Anticrm Platform Contributors.
// Copyright © 2021 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.
-->
<script lang="ts">
import contact, { formatName, Person } from '@anticrm/contact'
import { Ref } from '@anticrm/core'
import { getClient } from '@anticrm/presentation'
import type { Applicant } from '@anticrm/recruit'
import { Icon } from '@anticrm/ui'
import recruit from '../plugin'
export let value: Applicant
const client = getClient()
const shortLabel = client.getHierarchy().getClass(value._class).shortLabel?.toUpperCase()
let person: Person| undefined
$: client.findOne(contact.class.Person, { _id: value.attachedTo as Ref<Person> }).then(p => {
person = p
})
</script>
<div class='flex item'>
<Icon icon={recruit.icon.Application} size={'large'} />
<div class='ml-2'>
{shortLabel}-{value.number}
</div>
{#if person}
<div class='ml-1'>{formatName(person.name)}</div>
{/if}
</div>
<style lang="scss">
.item {
align-items: center;
}
</style>
+41 -1
View File
@@ -14,7 +14,7 @@
//
import type { Client, Doc } from '@anticrm/core'
import { OK, Resources, Severity, Status } from '@anticrm/platform'
import { IntlString, OK, Resources, Severity, Status, translate } from '@anticrm/platform'
import { Applicant } from '@anticrm/recruit'
import { showPopup } from '@anticrm/ui'
import ApplicationPresenter from './components/ApplicationPresenter.svelte'
@@ -29,6 +29,9 @@ import EditVacancy from './components/EditVacancy.svelte'
import KanbanCard from './components/KanbanCard.svelte'
import TemplatesIcon from './components/TemplatesIcon.svelte'
import recruit from './plugin'
import { ObjectSearchResult } from '@anticrm/presentation'
import task from '@anticrm/task'
import ApplicationItem from './components/ApplicationItem.svelte'
async function createApplication (object: Doc): Promise<void> {
showPopup(CreateApplication, { candidate: object._id, preserveCandidate: true })
@@ -51,6 +54,40 @@ export async function applicantValidator (applicant: Applicant, client: Client):
return OK
}
export async function queryApplication (client: Client, search: string): Promise<ObjectSearchResult[]> {
const _class = recruit.class.Applicant
const cl = client.getHierarchy().getClass(_class)
const shortLabel = (await translate(cl.shortLabel ?? '' as IntlString, {})).toUpperCase()
// Check number pattern
const sequence = (await client.findOne(task.class.Sequence, { attachedTo: _class }))?.sequence ?? 0
const named = new Map((await client.findAll(_class, { $search: search }, { limit: 200 })).map(e => [e._id, e]))
const nids: number[] = []
if (sequence > 0) {
for (let n = 0; n < sequence; n++) {
const v = `${n}`
if (v.includes(search)) {
nids.push(n)
}
}
const numbered = await client.findAll<Applicant>(_class, { number: { $in: nids } }, { limit: 200 })
for (const d of numbered) {
if (!named.has(d._id)) {
named.set(d._id, d)
}
}
}
return Array.from(named.values()).map(e => ({
doc: e,
title: `${shortLabel}-${e.number}`,
icon: task.icon.Task,
component: ApplicationItem
}))
}
export default async (): Promise<Resources> => ({
actionImpl: {
CreateApplication: createApplication
@@ -70,5 +107,8 @@ export default async (): Promise<Resources> => ({
Applications,
Candidates,
CreateCandidate
},
completion: {
ApplicationQuery: async (client: Client, query: string) => await queryApplication(client, query)
}
})
+2 -1
View File
@@ -63,6 +63,7 @@
"AllStates": "All states",
"DoneStates": "Done states",
"States": "States",
"ChooseAColor": "Choose a color"
"ChooseAColor": "Choose a color",
"SearchTask": "Search for task..."
}
}
@@ -0,0 +1,47 @@
<!--
// Copyright © 2020, 2021 Anticrm Platform Contributors.
// Copyright © 2021 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.
-->
<script lang="ts">
import { getClient } from '@anticrm/presentation'
import type { Issue, Task } from '@anticrm/task'
import { Icon } from '@anticrm/ui'
import task from '../plugin'
export let value: Task
const client = getClient()
const shortLabel = client.getHierarchy().getClass(value._class).shortLabel?.toUpperCase()
let name: string | undefined = undefined
$: if (client.getHierarchy().isDerived(value._class, task.class.Issue)) {
name = (value as Issue).name
}
</script>
<div class='flex item'>
<Icon icon={task.icon.Task} size={'large'} />
<div class='ml-2'>
{shortLabel}-{value.number}
</div>
{#if name}
<div class='ml-1'>{name}</div>
{/if}
</div>
<style lang="scss">
.item {
align-items: center;
}
</style>
+52 -18
View File
@@ -14,33 +14,31 @@
// limitations under the License.
//
import { Resources } from '@anticrm/platform'
import CreateTask from './components/CreateTask.svelte'
import CreateProject from './components/CreateProject.svelte'
import TaskPresenter from './components/TaskPresenter.svelte'
import KanbanCard from './components/KanbanCard.svelte'
import TemplatesIcon from './components/TemplatesIcon.svelte'
import EditIssue from './components/EditIssue.svelte'
import { Doc } from '@anticrm/core'
import { Class, Client, Doc, Ref } from '@anticrm/core'
import { IntlString, Resources, translate } from '@anticrm/platform'
import { getClient, MessageBox, ObjectSearchResult } from '@anticrm/presentation'
import task, { SpaceWithStates, Task, TodoItem } from '@anticrm/task'
import { showPopup } from '@anticrm/ui'
import { getClient, MessageBox } from '@anticrm/presentation'
import TaskItem from './components/TaskItem.svelte'
import CreateProject from './components/CreateProject.svelte'
import CreateTask from './components/CreateTask.svelte'
import EditIssue from './components/EditIssue.svelte'
import KanbanView from './components/kanban/KanbanView.svelte'
import StateEditor from './components/state/StateEditor.svelte'
import StatePresenter from './components/state/StatePresenter.svelte'
import KanbanCard from './components/KanbanCard.svelte'
import DoneStatePresenter from './components/state/DoneStatePresenter.svelte'
import EditStatuses from './components/state/EditStatuses.svelte'
import { SpaceWithStates, TodoItem } from '@anticrm/task'
import Todos from './components/todos/Todos.svelte'
import TodoItemPresenter from './components/todos/TodoItemPresenter.svelte'
import TodoStatePresenter from './components/todos/TodoStatePresenter.svelte'
import StateEditor from './components/state/StateEditor.svelte'
import StatePresenter from './components/state/StatePresenter.svelte'
import StatusTableView from './components/StatusTableView.svelte'
import TaskHeader from './components/TaskHeader.svelte'
import TaskPresenter from './components/TaskPresenter.svelte'
import TemplatesIcon from './components/TemplatesIcon.svelte'
import TodoItemPresenter from './components/todos/TodoItemPresenter.svelte'
import Todos from './components/todos/Todos.svelte'
import TodoStatePresenter from './components/todos/TodoStatePresenter.svelte'
export { default as KanbanTemplateEditor } from './components/kanban/KanbanTemplateEditor.svelte'
export { default as KanbanTemplateSelector } from './components/kanban/KanbanTemplateSelector.svelte'
export { default as Tasks } from './components/Tasks.svelte'
async function createTask (object: Doc): Promise<void> {
@@ -93,6 +91,39 @@ async function UnarchiveSpace (object: SpaceWithStates): Promise<void> {
)
}
export async function queryTask<D extends Task> (_class: Ref<Class<D>>, client: Client, search: string): Promise<ObjectSearchResult[]> {
const cl = client.getHierarchy().getClass(_class)
const shortLabel = (await translate(cl.shortLabel ?? '' as IntlString, {})).toUpperCase()
// Check number pattern
const sequence = (await client.findOne(task.class.Sequence, { attachedTo: _class }))?.sequence ?? 0
const named = new Map((await client.findAll(_class, { name: { $like: `%${search}%` } }, { limit: 200 })).map(e => [e._id, e]))
const nids: number[] = []
if (sequence > 0) {
for (let n = 0; n < sequence; n++) {
const v = `${n}`
if (v.includes(search)) {
nids.push(n)
}
}
const numbered = await client.findAll<Task>(_class, { number: { $in: nids } }, { limit: 200 }) as D[]
for (const d of numbered) {
if (!named.has(d._id)) {
named.set(d._id, d)
}
}
}
return Array.from(named.values()).map(e => ({
doc: e,
title: `${shortLabel}-${e.number}`,
icon: task.icon.Task,
component: TaskItem
}))
}
export default async (): Promise<Resources> => ({
component: {
CreateTask,
@@ -118,5 +149,8 @@ export default async (): Promise<Resources> => ({
TodoItemMarkUnDone: async (obj: TodoItem) => await toggleDone(false, obj),
ArchiveSpace,
UnarchiveSpace
},
completion: {
IssueQuery: async (client: Client, query: string) => await queryTask(task.class.Issue, client, query)
}
})
-2
View File
@@ -101,8 +101,6 @@ export interface Project extends SpaceWithStates {}
* @public
*/
export interface Issue extends Task {
number: number // Sequence number
name: string
description: string
+5
View File
@@ -536,6 +536,11 @@
"projectFolder": "packages/presentation",
"shouldPublish": true
},
{
"packageName": "@anticrm/model-presentation",
"projectFolder": "models/presentation",
"shouldPublish": true
},
{
"packageName": "@anticrm/client",
"projectFolder": "plugins/client",
+1 -1
View File
@@ -185,7 +185,7 @@ class TServerStorage implements ServerStorage {
): Promise<FindResult<T>> {
return await ctx.with('find-all', {}, (ctx) => {
const domain = this.hierarchy.getDomain(clazz)
if (query.$search !== undefined && query.$search.length > 0) {
if (query.$search !== undefined) {
return ctx.with('full-text-find-all', {}, (ctx) => this.fulltext.findAll(ctx, clazz, query, options))
}
return ctx.with('db-find-all', { _class: clazz, domain }, () =>