mirror of
https://github.com/hcengineering/platform.git
synced 2026-09-13 13:17:45 +02:00
New Activity - squashed (#4032)
Signed-off-by: Kristina Fefelova <kristin.fefelova@gmail.com> Co-authored-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
co-authored by
Andrey Sobolev
parent
119d65653a
commit
ea3eb4af83
+21
-21
@@ -37,29 +37,29 @@
|
||||
"prettier-plugin-svelte": "^3.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"commander": "^8.1.0",
|
||||
"@hcengineering/account": "^0.6.0",
|
||||
"@hcengineering/core": "^0.6.28",
|
||||
"@hcengineering/contact": "^0.6.20",
|
||||
"@hcengineering/tracker": "^0.6.13",
|
||||
"@hcengineering/model-all": "^0.6.0",
|
||||
"@hcengineering/model-telegram": "^0.6.0",
|
||||
"@hcengineering/telegram": "^0.6.14",
|
||||
"@hcengineering/client-resources": "^0.6.23",
|
||||
"ws": "^8.10.0",
|
||||
"@hcengineering/client": "^0.6.14",
|
||||
"@hcengineering/platform": "^0.6.9",
|
||||
"@hcengineering/model": "^0.6.7",
|
||||
"@hcengineering/recruit": "^0.6.21",
|
||||
"faker": "~5.5.3",
|
||||
"@hcengineering/model-recruit": "^0.6.0",
|
||||
"@hcengineering/chunter": "^0.6.12",
|
||||
"pdfkit": "~0.13.0",
|
||||
"@hcengineering/attachment": "^0.6.9",
|
||||
"@types/pdfkit": "~0.12.3",
|
||||
"@hcengineering/task": "^0.6.13",
|
||||
"jpeg-js": "~0.4.3",
|
||||
"@hcengineering/client": "^0.6.14",
|
||||
"@hcengineering/client-resources": "^0.6.23",
|
||||
"@hcengineering/contact": "^0.6.20",
|
||||
"@hcengineering/core": "^0.6.28",
|
||||
"@hcengineering/minio": "^0.6.0",
|
||||
"@hcengineering/model": "^0.6.7",
|
||||
"@hcengineering/model-all": "^0.6.0",
|
||||
"@hcengineering/model-recruit": "^0.6.0",
|
||||
"@hcengineering/model-telegram": "^0.6.0",
|
||||
"@hcengineering/platform": "^0.6.9",
|
||||
"@hcengineering/recruit": "^0.6.21",
|
||||
"@hcengineering/server-token": "^0.6.7",
|
||||
"@hcengineering/minio": "^0.6.0"
|
||||
"@hcengineering/task": "^0.6.13",
|
||||
"@hcengineering/telegram": "^0.6.14",
|
||||
"@hcengineering/tracker": "^0.6.13",
|
||||
"@hcengineering/notification": "^0.6.16",
|
||||
"@types/pdfkit": "~0.12.3",
|
||||
"commander": "^8.1.0",
|
||||
"faker": "~5.5.3",
|
||||
"jpeg-js": "~0.4.3",
|
||||
"pdfkit": "~0.13.0",
|
||||
"ws": "^8.10.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import chunter, { Comment } from '@hcengineering/chunter'
|
||||
import { AttachedData, Class, Doc, generateId, Ref, Space, TxOperations } from '@hcengineering/core'
|
||||
import notification, { ChatMessage } from '@hcengineering/notification'
|
||||
import faker from 'faker'
|
||||
|
||||
export interface CommentOptions {
|
||||
@@ -20,17 +20,25 @@ export async function addComments<T extends Doc> (
|
||||
): Promise<void> {
|
||||
const commentsCount = options.min + faker.datatype.number(options.max)
|
||||
for (let i = 0; i < commentsCount; i++) {
|
||||
const commentId = `candidate-comment-${generateId()}-${i}` as Ref<Comment>
|
||||
const commentId = `candidate-comment-${generateId()}-${i}` as Ref<ChatMessage>
|
||||
|
||||
const commentData: AttachedData<Comment> = {
|
||||
const commentData: AttachedData<ChatMessage> = {
|
||||
message: faker.lorem.paragraphs(options.paragraphMin + faker.datatype.number(options.paragraphMax))
|
||||
}
|
||||
await client.addCollection(chunter.class.Comment, space, objectId, _class, collection, commentData, commentId)
|
||||
await client.addCollection(
|
||||
notification.class.ChatMessage,
|
||||
space,
|
||||
objectId,
|
||||
_class,
|
||||
collection,
|
||||
commentData,
|
||||
commentId
|
||||
)
|
||||
|
||||
if (faker.datatype.number(100) > options.updateFactor) {
|
||||
const updateMsg = faker.lorem.paragraphs(options.paragraphMin + faker.datatype.number(options.paragraphMax))
|
||||
|
||||
await client.updateCollection(chunter.class.Comment, space, commentId, objectId, _class, collection, {
|
||||
await client.updateCollection(notification.class.ChatMessage, space, commentId, objectId, _class, collection, {
|
||||
message: updateMsg
|
||||
})
|
||||
}
|
||||
|
||||
+56
-56
@@ -46,81 +46,81 @@
|
||||
"prettier-plugin-svelte": "^3.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"mongodb": "^4.11.0",
|
||||
"commander": "^8.1.0",
|
||||
"@hcengineering/account": "^0.6.0",
|
||||
"@hcengineering/core": "^0.6.28",
|
||||
"@hcengineering/contact": "^0.6.20",
|
||||
"@hcengineering/model-all": "^0.6.0",
|
||||
"@hcengineering/model-telegram": "^0.6.0",
|
||||
"@hcengineering/telegram": "^0.6.14",
|
||||
"@hcengineering/client-resources": "^0.6.23",
|
||||
"ws": "^8.10.0",
|
||||
"@hcengineering/client": "^0.6.14",
|
||||
"@hcengineering/platform": "^0.6.9",
|
||||
"@hcengineering/model": "^0.6.7",
|
||||
"fast-equals": "^2.0.3",
|
||||
"@elastic/elasticsearch": "^7.14.0",
|
||||
"@hcengineering/account": "^0.6.0",
|
||||
"@hcengineering/attachment": "^0.6.9",
|
||||
"@hcengineering/client": "^0.6.14",
|
||||
"@hcengineering/client-resources": "^0.6.23",
|
||||
"@hcengineering/contact": "^0.6.20",
|
||||
"@hcengineering/core": "^0.6.28",
|
||||
"@hcengineering/dev-storage": "^0.6.6",
|
||||
"@hcengineering/elastic": "^0.6.0",
|
||||
"@hcengineering/server-core": "^0.6.1",
|
||||
"@hcengineering/server-token": "^0.6.7",
|
||||
"@hcengineering/lead": "^0.6.0",
|
||||
"@hcengineering/minio": "^0.6.0",
|
||||
"@hcengineering/model": "^0.6.7",
|
||||
"@hcengineering/model-all": "^0.6.0",
|
||||
"@hcengineering/model-attachment": "^0.6.0",
|
||||
"@hcengineering/model-contact": "^0.6.1",
|
||||
"@hcengineering/model-recruit": "^0.6.0",
|
||||
"@hcengineering/model-telegram": "^0.6.0",
|
||||
"@hcengineering/mongo": "^0.6.1",
|
||||
"@hcengineering/dev-storage": "^0.6.6",
|
||||
"@hcengineering/notification": "^0.6.16",
|
||||
"@hcengineering/openai": "^0.6.0",
|
||||
"@hcengineering/platform": "^0.6.9",
|
||||
"@hcengineering/recruit": "^0.6.21",
|
||||
"@hcengineering/rekoni": "^0.6.0",
|
||||
"@hcengineering/server-attachment": "^0.6.1",
|
||||
"@hcengineering/server-attachment-resources": "^0.6.0",
|
||||
"xml2js": "~0.4.23",
|
||||
"@hcengineering/model-recruit": "^0.6.0",
|
||||
"@hcengineering/recruit": "^0.6.21",
|
||||
"@hcengineering/task": "^0.6.13",
|
||||
"@hcengineering/chunter": "^0.6.12",
|
||||
"mime-types": "~2.1.34",
|
||||
"@hcengineering/attachment": "^0.6.9",
|
||||
"@hcengineering/server-contact": "^0.6.1",
|
||||
"@hcengineering/server-contact-resources": "^0.6.0",
|
||||
"@hcengineering/server-notification": "^0.6.1",
|
||||
"@hcengineering/server-notification-resources": "^0.6.0",
|
||||
"@hcengineering/server-setting": "^0.6.0",
|
||||
"@hcengineering/server-setting-resources": "^0.6.0",
|
||||
"@hcengineering/server-tool": "^0.6.0",
|
||||
"@hcengineering/server-backup": "^0.6.0",
|
||||
"@hcengineering/server-calendar": "^0.6.0",
|
||||
"@hcengineering/server-calendar-resources": "^0.6.0",
|
||||
"@hcengineering/server-chunter": "^0.6.0",
|
||||
"@hcengineering/server-chunter-resources": "^0.6.0",
|
||||
"@hcengineering/server-contact": "^0.6.1",
|
||||
"@hcengineering/server-contact-resources": "^0.6.0",
|
||||
"@hcengineering/server-core": "^0.6.1",
|
||||
"@hcengineering/server-gmail": "^0.6.0",
|
||||
"@hcengineering/server-gmail-resources": "^0.6.0",
|
||||
"@hcengineering/server-hr": "^0.6.0",
|
||||
"@hcengineering/server-hr-resources": "^0.6.0",
|
||||
"@hcengineering/server-inventory": "^0.6.1",
|
||||
"@hcengineering/server-inventory-resources": "^0.6.0",
|
||||
"@hcengineering/server-lead": "^0.6.0",
|
||||
"@hcengineering/server-lead-resources": "^0.6.0",
|
||||
"@hcengineering/server-notification": "^0.6.1",
|
||||
"@hcengineering/server-notification-resources": "^0.6.0",
|
||||
"@hcengineering/server-recruit": "^0.6.0",
|
||||
"@hcengineering/server-recruit-resources": "^0.6.0",
|
||||
"@hcengineering/server-task": "^0.6.0",
|
||||
"@hcengineering/server-task-resources": "^0.6.0",
|
||||
"@hcengineering/server-tracker": "^0.6.0",
|
||||
"@hcengineering/server-tracker-resources": "^0.6.0",
|
||||
"@hcengineering/server-tags": "^0.6.0",
|
||||
"@hcengineering/server-tags-resources": "^0.6.0",
|
||||
"@hcengineering/server-calendar": "^0.6.0",
|
||||
"@hcengineering/server-calendar-resources": "^0.6.0",
|
||||
"@hcengineering/server-gmail": "^0.6.0",
|
||||
"@hcengineering/server-gmail-resources": "^0.6.0",
|
||||
"@hcengineering/server-telegram": "^0.6.0",
|
||||
"@hcengineering/server-telegram-resources": "^0.6.0",
|
||||
"@hcengineering/server-hr": "^0.6.0",
|
||||
"@hcengineering/server-hr-resources": "^0.6.0",
|
||||
"@hcengineering/server-request": "^0.6.0",
|
||||
"@hcengineering/server-request-resources": "^0.6.0",
|
||||
"@hcengineering/server-setting": "^0.6.0",
|
||||
"@hcengineering/server-setting-resources": "^0.6.0",
|
||||
"@hcengineering/server-tags": "^0.6.0",
|
||||
"@hcengineering/server-tags-resources": "^0.6.0",
|
||||
"@hcengineering/server-task": "^0.6.0",
|
||||
"@hcengineering/server-task-resources": "^0.6.0",
|
||||
"@hcengineering/server-telegram": "^0.6.0",
|
||||
"@hcengineering/server-telegram-resources": "^0.6.0",
|
||||
"@hcengineering/server-token": "^0.6.7",
|
||||
"@hcengineering/server-tool": "^0.6.0",
|
||||
"@hcengineering/server-tracker": "^0.6.0",
|
||||
"@hcengineering/server-tracker-resources": "^0.6.0",
|
||||
"@hcengineering/server-view": "^0.6.0",
|
||||
"@hcengineering/server-view-resources": "^0.6.0",
|
||||
"@hcengineering/rekoni": "^0.6.0",
|
||||
"got": "^11.8.3",
|
||||
"@hcengineering/tags": "^0.6.12",
|
||||
"@hcengineering/server-backup": "^0.6.0",
|
||||
"csv-parse": "~5.1.0",
|
||||
"@hcengineering/lead": "^0.6.0",
|
||||
"email-addresses": "^5.0.0",
|
||||
"libphonenumber-js": "^1.9.46",
|
||||
"@hcengineering/setting": "^0.6.11",
|
||||
"@hcengineering/minio": "^0.6.0",
|
||||
"@hcengineering/openai": "^0.6.0",
|
||||
"@hcengineering/tracker": "^0.6.13"
|
||||
"@hcengineering/tags": "^0.6.12",
|
||||
"@hcengineering/task": "^0.6.13",
|
||||
"@hcengineering/telegram": "^0.6.14",
|
||||
"@hcengineering/tracker": "^0.6.13",
|
||||
"commander": "^8.1.0",
|
||||
"csv-parse": "~5.1.0",
|
||||
"email-addresses": "^5.0.0",
|
||||
"fast-equals": "^2.0.3",
|
||||
"got": "^11.8.3",
|
||||
"libphonenumber-js": "^1.9.46",
|
||||
"mime-types": "~2.1.34",
|
||||
"mongodb": "^4.11.0",
|
||||
"ws": "^8.10.0",
|
||||
"xml2js": "~0.4.23"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
//
|
||||
|
||||
import attachment from '@hcengineering/attachment'
|
||||
import chunter, { Comment } from '@hcengineering/chunter'
|
||||
import contact from '@hcengineering/contact'
|
||||
import { deepEqual } from 'fast-equals'
|
||||
import core, {
|
||||
@@ -39,6 +38,7 @@ import { connect } from '@hcengineering/server-tool'
|
||||
import tracker from '@hcengineering/tracker'
|
||||
import tags, { TagCategory, TagElement, TagReference } from '@hcengineering/tags'
|
||||
import { MongoClient } from 'mongodb'
|
||||
import notification, { ChatMessage } from '@hcengineering/notification'
|
||||
|
||||
export const DOMAIN_COMMENT = 'comment' as Domain
|
||||
|
||||
@@ -322,11 +322,11 @@ export async function fixCommentDoubleIdCreate (workspaceId: WorkspaceId, transa
|
||||
try {
|
||||
const commentTxes = await connection.findAll(core.class.TxCollectionCUD, {
|
||||
'tx._class': core.class.TxCreateDoc,
|
||||
'tx.objectClass': chunter.class.Comment
|
||||
'tx.objectClass': notification.class.ChatMessage
|
||||
})
|
||||
const commentTxesRemoved = await connection.findAll(core.class.TxCollectionCUD, {
|
||||
'tx._class': core.class.TxRemoveDoc,
|
||||
'tx.objectClass': chunter.class.Comment
|
||||
'tx.objectClass': notification.class.ChatMessage
|
||||
})
|
||||
const removed = new Map(commentTxesRemoved.map((it) => [it.tx.objectId, it]))
|
||||
// Do not checked removed
|
||||
@@ -341,7 +341,7 @@ export async function fixCommentDoubleIdCreate (workspaceId: WorkspaceId, transa
|
||||
objSet.add(cid)
|
||||
if (has) {
|
||||
// We have found duplicate one, let's rename it.
|
||||
const doc = TxProcessor.createDoc2Doc<Comment>(c.tx as unknown as TxCreateDoc<Comment>)
|
||||
const doc = TxProcessor.createDoc2Doc<ChatMessage>(c.tx as unknown as TxCreateDoc<ChatMessage>)
|
||||
if (doc.message !== '' && doc.message.trim() !== '<p></p>') {
|
||||
await connection.clean(DOMAIN_TX, [c._id])
|
||||
if (oldValue.get(cid) === doc.message.trim()) {
|
||||
@@ -351,7 +351,7 @@ export async function fixCommentDoubleIdCreate (workspaceId: WorkspaceId, transa
|
||||
console.log('renaming', cid, doc.message)
|
||||
// Remove previous transaction.
|
||||
c.tx.objectId = generateId()
|
||||
doc._id = c.tx.objectId as Ref<Comment>
|
||||
doc._id = c.tx.objectId as Ref<ChatMessage>
|
||||
await connection.upload(DOMAIN_TX, [c])
|
||||
// Also we need to create snapsot
|
||||
await connection.upload(DOMAIN_COMMENT, [doc])
|
||||
|
||||
@@ -15,23 +15,22 @@
|
||||
"devDependencies": {
|
||||
"@hcengineering/platform-rig": "^0.6.0",
|
||||
"@typescript-eslint/eslint-plugin": "^6.11.0",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-promise": "^6.1.1",
|
||||
"eslint-plugin-n": "^15.4.0",
|
||||
"eslint": "^8.54.0",
|
||||
"@typescript-eslint/parser": "^6.11.0",
|
||||
"eslint": "^8.54.0",
|
||||
"eslint-config-standard-with-typescript": "^40.0.0",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-n": "^15.4.0",
|
||||
"eslint-plugin-promise": "^6.1.1",
|
||||
"prettier": "^3.1.0",
|
||||
"prettier-plugin-svelte": "^3.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hcengineering/activity": "^0.6.0",
|
||||
"@hcengineering/activity-resources": "^0.6.1",
|
||||
"@hcengineering/core": "^0.6.28",
|
||||
"@hcengineering/model": "^0.6.7",
|
||||
"@hcengineering/contact": "^0.6.20",
|
||||
"@hcengineering/platform": "^0.6.9",
|
||||
"@hcengineering/model-core": "^0.6.0",
|
||||
"@hcengineering/activity": "^0.6.0",
|
||||
"@hcengineering/ui": "^0.6.11",
|
||||
"@hcengineering/activity-resources": "^0.6.1"
|
||||
"@hcengineering/platform": "^0.6.9",
|
||||
"@hcengineering/ui": "^0.6.11"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,18 +13,11 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import type {
|
||||
ActivityExtension,
|
||||
ActivityExtensionKind,
|
||||
ActivityFilter,
|
||||
DisplayTx,
|
||||
ExtraActivityComponent,
|
||||
TxViewlet
|
||||
} from '@hcengineering/activity'
|
||||
import type { TxViewlet } from '@hcengineering/activity'
|
||||
import core, { DOMAIN_MODEL, type Class, type Doc, type DocumentQuery, type Ref, type Tx } from '@hcengineering/core'
|
||||
import { Mixin, Model, type Builder } from '@hcengineering/model'
|
||||
import { TClass, TDoc } from '@hcengineering/model-core'
|
||||
import type { Asset, IntlString, Resource } from '@hcengineering/platform'
|
||||
import { Model, type Builder } from '@hcengineering/model'
|
||||
import { TDoc } from '@hcengineering/model-core'
|
||||
import type { Asset, IntlString } from '@hcengineering/platform'
|
||||
import { type AnyComponent } from '@hcengineering/ui/src/types'
|
||||
|
||||
import activity from './plugin'
|
||||
@@ -46,31 +39,8 @@ export class TTxViewlet extends TDoc implements TxViewlet {
|
||||
hideOnRemove!: boolean
|
||||
}
|
||||
|
||||
@Model(activity.class.ActivityFilter, core.class.Doc, DOMAIN_MODEL)
|
||||
export class TActivityFilter extends TDoc implements ActivityFilter {
|
||||
label!: IntlString
|
||||
filter!: Resource<(tx: DisplayTx, _class?: Ref<Doc>) => boolean>
|
||||
}
|
||||
|
||||
@Model(activity.class.ActivityExtension, core.class.Doc, DOMAIN_MODEL)
|
||||
export class TActivityExtension extends TDoc implements ActivityExtension {
|
||||
ofClass!: Ref<Class<Doc>>
|
||||
components?: Partial<Record<ActivityExtensionKind, AnyComponent>>
|
||||
mentionClass?: Ref<Class<Doc>>
|
||||
}
|
||||
|
||||
@Mixin(activity.mixin.ExtraActivityComponent, core.class.Class)
|
||||
export class TExtraActivityComponent extends TClass implements ExtraActivityComponent {
|
||||
component!: AnyComponent
|
||||
}
|
||||
|
||||
export function createModel (builder: Builder): void {
|
||||
builder.createModel(TTxViewlet, TActivityFilter, TExtraActivityComponent, TActivityExtension)
|
||||
|
||||
builder.createDoc(activity.class.ActivityFilter, core.space.Model, {
|
||||
label: activity.string.Attributes,
|
||||
filter: activity.filter.AttributeFilter
|
||||
})
|
||||
builder.createModel(TTxViewlet)
|
||||
}
|
||||
|
||||
export default activity
|
||||
|
||||
@@ -12,18 +12,8 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
import type { DisplayTx } from '@hcengineering/activity'
|
||||
import { activityId } from '@hcengineering/activity'
|
||||
import activity from '@hcengineering/activity-resources/src/plugin'
|
||||
import type { Resource, IntlString } from '@hcengineering/platform'
|
||||
import { mergeIds } from '@hcengineering/platform'
|
||||
import { type Doc, type Ref } from '@hcengineering/core'
|
||||
|
||||
export default mergeIds(activityId, activity, {
|
||||
filter: {
|
||||
AttributeFilter: '' as Resource<(tx: DisplayTx, _class?: Ref<Doc>) => boolean>
|
||||
},
|
||||
string: {
|
||||
Attributes: '' as IntlString
|
||||
}
|
||||
})
|
||||
export default mergeIds(activityId, activity, {})
|
||||
|
||||
@@ -36,6 +36,7 @@ import { boardOperation } from '@hcengineering/model-board'
|
||||
import { hrOperation } from '@hcengineering/model-hr'
|
||||
import { bitrixOperation } from '@hcengineering/model-bitrix'
|
||||
import { calendarOperation } from '@hcengineering/model-calendar'
|
||||
import { notificationServerOperation } from '@hcengineering/model-server-notification'
|
||||
|
||||
export const migrateOperations: [string, MigrateOperation][] = [
|
||||
['core', coreOperation],
|
||||
@@ -58,5 +59,6 @@ export const migrateOperations: [string, MigrateOperation][] = [
|
||||
['board', boardOperation],
|
||||
['hr', hrOperation],
|
||||
['bitrix', bitrixOperation],
|
||||
['inventiry', inventoryOperation]
|
||||
['inventiry', inventoryOperation],
|
||||
['notificationServer', notificationServerOperation]
|
||||
]
|
||||
|
||||
@@ -15,26 +15,27 @@
|
||||
"devDependencies": {
|
||||
"@hcengineering/platform-rig": "^0.6.0",
|
||||
"@typescript-eslint/eslint-plugin": "^6.11.0",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-promise": "^6.1.1",
|
||||
"eslint-plugin-n": "^15.4.0",
|
||||
"eslint": "^8.54.0",
|
||||
"@typescript-eslint/parser": "^6.11.0",
|
||||
"eslint": "^8.54.0",
|
||||
"eslint-config-standard-with-typescript": "^40.0.0",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-n": "^15.4.0",
|
||||
"eslint-plugin-promise": "^6.1.1",
|
||||
"prettier": "^3.1.0",
|
||||
"prettier-plugin-svelte": "^3.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hcengineering/core": "^0.6.28",
|
||||
"@hcengineering/model": "^0.6.7",
|
||||
"@hcengineering/ui": "^0.6.11",
|
||||
"@hcengineering/activity": "^0.6.0",
|
||||
"@hcengineering/attachment": "^0.6.9",
|
||||
"@hcengineering/attachment-resources": "^0.6.0",
|
||||
"@hcengineering/platform": "^0.6.9",
|
||||
"@hcengineering/core": "^0.6.28",
|
||||
"@hcengineering/model": "^0.6.7",
|
||||
"@hcengineering/model-core": "^0.6.0",
|
||||
"@hcengineering/model-view": "^0.6.0",
|
||||
"@hcengineering/activity": "^0.6.0",
|
||||
"@hcengineering/model-preference": "^0.6.0",
|
||||
"@hcengineering/model-view": "^0.6.0",
|
||||
"@hcengineering/notification": "^0.6.16",
|
||||
"@hcengineering/platform": "^0.6.9",
|
||||
"@hcengineering/ui": "^0.6.11",
|
||||
"@hcengineering/view": "^0.6.9"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ import core, { TAttachedDoc } from '@hcengineering/model-core'
|
||||
import preference, { TPreference } from '@hcengineering/model-preference'
|
||||
import view, { createAction } from '@hcengineering/model-view'
|
||||
import attachment from './plugin'
|
||||
import notification from '@hcengineering/notification'
|
||||
|
||||
export { attachmentId } from '@hcengineering/attachment'
|
||||
export { attachmentOperation } from './migration'
|
||||
@@ -122,7 +123,33 @@ export function createModel (builder: Builder): void {
|
||||
attachment.ids.TxAttachmentRemove
|
||||
)
|
||||
|
||||
builder.createDoc(activity.class.ActivityFilter, core.space.Model, {
|
||||
builder.createDoc(
|
||||
notification.class.DocUpdateMessageViewlet,
|
||||
core.space.Model,
|
||||
{
|
||||
objectClass: attachment.class.Attachment,
|
||||
action: 'create',
|
||||
icon: attachment.icon.Attachment,
|
||||
label: attachment.string.AddAttachment,
|
||||
component: attachment.notification.NotificationAttachmentChanged
|
||||
},
|
||||
attachment.ids.NotificationAttachmentCreated
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
notification.class.DocUpdateMessageViewlet,
|
||||
core.space.Model,
|
||||
{
|
||||
objectClass: attachment.class.Attachment,
|
||||
action: 'remove',
|
||||
icon: attachment.icon.Attachment,
|
||||
label: attachment.string.RemovedAttachment,
|
||||
component: attachment.notification.NotificationAttachmentChanged
|
||||
},
|
||||
attachment.ids.NotificationAttachmentRemoved
|
||||
)
|
||||
|
||||
builder.createDoc(notification.class.ActivityMessagesFilter, core.space.Model, {
|
||||
label: attachment.string.FilterAttachments,
|
||||
filter: attachment.filter.AttachmentsFilter
|
||||
})
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import type { DisplayTx, TxViewlet } from '@hcengineering/activity'
|
||||
import type { TxViewlet } from '@hcengineering/activity'
|
||||
import { attachmentId } from '@hcengineering/attachment'
|
||||
import attachment from '@hcengineering/attachment-resources/src/plugin'
|
||||
import type { Ref, Doc } from '@hcengineering/core'
|
||||
@@ -21,6 +21,7 @@ import type { IntlString, Resource } from '@hcengineering/platform'
|
||||
import { mergeIds } from '@hcengineering/platform'
|
||||
import type { AnyComponent } from '@hcengineering/ui/src/types'
|
||||
import type { ActionCategory } from '@hcengineering/view'
|
||||
import { type ActivityMessage, type DocUpdateMessageViewlet } from '@hcengineering/notification'
|
||||
|
||||
export default mergeIds(attachmentId, attachment, {
|
||||
component: {
|
||||
@@ -45,15 +46,20 @@ export default mergeIds(attachmentId, attachment, {
|
||||
},
|
||||
ids: {
|
||||
TxAttachmentCreate: '' as Ref<TxViewlet>,
|
||||
TxAttachmentRemove: '' as Ref<TxViewlet>
|
||||
TxAttachmentRemove: '' as Ref<TxViewlet>,
|
||||
NotificationAttachmentCreated: '' as Ref<DocUpdateMessageViewlet>,
|
||||
NotificationAttachmentRemoved: '' as Ref<DocUpdateMessageViewlet>
|
||||
},
|
||||
activity: {
|
||||
TxAttachmentCreate: '' as AnyComponent
|
||||
},
|
||||
notification: {
|
||||
NotificationAttachmentChanged: '' as AnyComponent
|
||||
},
|
||||
category: {
|
||||
Attachments: '' as Ref<ActionCategory>
|
||||
},
|
||||
filter: {
|
||||
AttachmentsFilter: '' as Resource<(tx: DisplayTx, _class?: Ref<Doc>) => boolean>
|
||||
AttachmentsFilter: '' as Resource<(message: ActivityMessage, _class?: Ref<Doc>) => boolean>
|
||||
}
|
||||
})
|
||||
|
||||
@@ -243,6 +243,18 @@ export function createModel (builder: Builder): void {
|
||||
calendar.ids.ReminderViewlet
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
notification.class.DocUpdateMessageViewlet,
|
||||
core.space.Model,
|
||||
{
|
||||
objectClass: calendar.class.Event,
|
||||
icon: calendar.icon.Reminder,
|
||||
action: 'update',
|
||||
hideIfRemoved: true
|
||||
},
|
||||
calendar.ids.UpdateRemainderNotificationViewlet
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
view.class.ViewletDescriptor,
|
||||
core.space.Model,
|
||||
|
||||
@@ -17,7 +17,7 @@ import { type TxViewlet } from '@hcengineering/activity'
|
||||
import { calendarId } from '@hcengineering/calendar'
|
||||
import calendar from '@hcengineering/calendar-resources/src/plugin'
|
||||
import { type Ref } from '@hcengineering/core'
|
||||
import { type NotificationGroup } from '@hcengineering/notification'
|
||||
import { type DocUpdateMessageViewlet, type NotificationGroup } from '@hcengineering/notification'
|
||||
import type { IntlString } from '@hcengineering/platform'
|
||||
import { mergeIds } from '@hcengineering/platform'
|
||||
import { type AnyComponent } from '@hcengineering/ui/src/types'
|
||||
@@ -66,6 +66,7 @@ export default mergeIds(calendarId, calendar, {
|
||||
},
|
||||
ids: {
|
||||
ReminderViewlet: '' as Ref<TxViewlet>,
|
||||
UpdateRemainderNotificationViewlet: '' as Ref<DocUpdateMessageViewlet>,
|
||||
CalendarNotificationGroup: '' as Ref<NotificationGroup>
|
||||
}
|
||||
})
|
||||
|
||||
+16
-15
@@ -15,32 +15,33 @@
|
||||
"devDependencies": {
|
||||
"@hcengineering/platform-rig": "^0.6.0",
|
||||
"@typescript-eslint/eslint-plugin": "^6.11.0",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-promise": "^6.1.1",
|
||||
"eslint-plugin-n": "^15.4.0",
|
||||
"eslint": "^8.54.0",
|
||||
"@typescript-eslint/parser": "^6.11.0",
|
||||
"eslint": "^8.54.0",
|
||||
"eslint-config-standard-with-typescript": "^40.0.0",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-n": "^15.4.0",
|
||||
"eslint-plugin-promise": "^6.1.1",
|
||||
"prettier": "^3.1.0",
|
||||
"prettier-plugin-svelte": "^3.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hcengineering/core": "^0.6.28",
|
||||
"@hcengineering/model": "^0.6.7",
|
||||
"@hcengineering/ui": "^0.6.11",
|
||||
"@hcengineering/view": "^0.6.9",
|
||||
"@hcengineering/model-attachment": "^0.6.0",
|
||||
"@hcengineering/contact": "^0.6.20",
|
||||
"@hcengineering/activity": "^0.6.0",
|
||||
"@hcengineering/attachment": "^0.6.9",
|
||||
"@hcengineering/chunter": "^0.6.12",
|
||||
"@hcengineering/chunter-resources": "^0.6.0",
|
||||
"@hcengineering/platform": "^0.6.9",
|
||||
"@hcengineering/contact": "^0.6.20",
|
||||
"@hcengineering/core": "^0.6.28",
|
||||
"@hcengineering/model": "^0.6.7",
|
||||
"@hcengineering/model-attachment": "^0.6.0",
|
||||
"@hcengineering/model-core": "^0.6.0",
|
||||
"@hcengineering/model-notification": "^0.6.0",
|
||||
"@hcengineering/model-preference": "^0.6.0",
|
||||
"@hcengineering/model-view": "^0.6.0",
|
||||
"@hcengineering/model-workbench": "^0.6.1",
|
||||
"@hcengineering/model-notification": "^0.6.0",
|
||||
"@hcengineering/notification": "^0.6.16",
|
||||
"@hcengineering/activity": "^0.6.0",
|
||||
"@hcengineering/workbench": "^0.6.9",
|
||||
"@hcengineering/model-preference": "^0.6.0"
|
||||
"@hcengineering/platform": "^0.6.9",
|
||||
"@hcengineering/ui": "^0.6.11",
|
||||
"@hcengineering/view": "^0.6.9",
|
||||
"@hcengineering/workbench": "^0.6.9"
|
||||
}
|
||||
}
|
||||
|
||||
+109
-48
@@ -195,6 +195,8 @@ export function createModel (builder: Builder, options = { addApplication: true
|
||||
const spaceClasses = [chunter.class.Channel, chunter.class.DirectMessage]
|
||||
|
||||
spaceClasses.forEach((spaceClass) => {
|
||||
builder.mixin(spaceClass, core.class.Class, notification.mixin.ActivityDoc, {})
|
||||
|
||||
builder.mixin(spaceClass, core.class.Class, workbench.mixin.SpaceView, {
|
||||
view: {
|
||||
class: chunter.class.Message
|
||||
@@ -475,18 +477,6 @@ export function createModel (builder: Builder, options = { addApplication: true
|
||||
)
|
||||
}
|
||||
|
||||
builder.mixin(chunter.class.Comment, core.class.Class, view.mixin.ObjectPresenter, {
|
||||
presenter: chunter.component.CommentPresenter
|
||||
})
|
||||
|
||||
builder.mixin(chunter.class.Comment, core.class.Class, view.mixin.CollectionPresenter, {
|
||||
presenter: chunter.component.CommentsPresenter
|
||||
})
|
||||
|
||||
builder.mixin(chunter.class.Comment, core.class.Class, view.mixin.ObjectPanel, {
|
||||
component: chunter.component.CommentPanel
|
||||
})
|
||||
|
||||
builder.createDoc(
|
||||
activity.class.TxViewlet,
|
||||
core.space.Model,
|
||||
@@ -503,6 +493,22 @@ export function createModel (builder: Builder, options = { addApplication: true
|
||||
chunter.ids.TxCommentCreate
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
activity.class.TxViewlet,
|
||||
core.space.Model,
|
||||
{
|
||||
objectClass: notification.class.ChatMessage,
|
||||
icon: chunter.icon.Chunter,
|
||||
txClass: core.class.TxCreateDoc,
|
||||
component: chunter.activity.TxCommentCreate,
|
||||
label: chunter.string.LeftComment,
|
||||
display: 'content',
|
||||
editable: true,
|
||||
hideOnRemove: true
|
||||
},
|
||||
chunter.ids.TxChatMessageCreate
|
||||
)
|
||||
|
||||
createAction(
|
||||
builder,
|
||||
{
|
||||
@@ -606,6 +612,19 @@ export function createModel (builder: Builder, options = { addApplication: true
|
||||
chunter.ids.TxCommentRemove
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
activity.class.TxViewlet,
|
||||
core.space.Model,
|
||||
{
|
||||
objectClass: notification.class.ChatMessage,
|
||||
icon: chunter.icon.Chunter,
|
||||
txClass: core.class.TxRemoveDoc,
|
||||
display: 'inline',
|
||||
hideOnRemove: true
|
||||
},
|
||||
chunter.ids.TxChatMessageRemove
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
activity.class.TxViewlet,
|
||||
core.space.Model,
|
||||
@@ -629,9 +648,9 @@ export function createModel (builder: Builder, options = { addApplication: true
|
||||
objectClass: chunter.class.Backlink,
|
||||
icon: chunter.icon.Chunter,
|
||||
txClass: core.class.TxCreateDoc,
|
||||
component: chunter.activity.TxBacklinkCreate,
|
||||
component: chunter.component.BacklinkContent,
|
||||
label: chunter.string.MentionedIn,
|
||||
labelComponent: chunter.activity.TxBacklinkReference,
|
||||
labelComponent: chunter.component.BacklinkReference,
|
||||
display: 'emphasized',
|
||||
editable: false,
|
||||
hideOnRemove: true
|
||||
@@ -653,17 +672,7 @@ export function createModel (builder: Builder, options = { addApplication: true
|
||||
chunter.ids.TxBacklinkRemove
|
||||
)
|
||||
|
||||
builder.createDoc(activity.class.ActivityFilter, core.space.Model, {
|
||||
label: chunter.string.FilterComments,
|
||||
filter: chunter.filter.CommentsFilter
|
||||
})
|
||||
|
||||
builder.createDoc(activity.class.ActivityFilter, core.space.Model, {
|
||||
label: chunter.string.FilterPinnedComments,
|
||||
filter: chunter.filter.PinnedCommentsFilter
|
||||
})
|
||||
|
||||
builder.createDoc(activity.class.ActivityFilter, core.space.Model, {
|
||||
builder.createDoc(notification.class.ActivityMessagesFilter, core.space.Model, {
|
||||
label: chunter.string.FilterBacklinks,
|
||||
filter: chunter.filter.BacklinksFilter
|
||||
})
|
||||
@@ -750,38 +759,41 @@ export function createModel (builder: Builder, options = { addApplication: true
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
activity.class.ActivityExtension,
|
||||
notification.class.ActivityMessageExtension,
|
||||
core.space.Model,
|
||||
{
|
||||
ofClass: chunter.class.Comment,
|
||||
components: {
|
||||
footer: chunter.component.CommentReactions,
|
||||
action: chunter.component.ReactionsAction
|
||||
}
|
||||
ofMessage: notification.class.DocUpdateMessage,
|
||||
components: [
|
||||
{
|
||||
kind: 'footer',
|
||||
component: chunter.component.ReactionsPresenter
|
||||
},
|
||||
{
|
||||
kind: 'action',
|
||||
component: chunter.component.ActivityMessageReactionsAction
|
||||
}
|
||||
]
|
||||
},
|
||||
chunter.ids.ActivityExtension
|
||||
chunter.ids.DocUpdateMessageExtension
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
activity.class.ActivityExtension,
|
||||
notification.class.ActivityMessageExtension,
|
||||
core.space.Model,
|
||||
{
|
||||
ofClass: chunter.class.Comment,
|
||||
components: {
|
||||
action: chunter.component.PinComment
|
||||
}
|
||||
ofMessage: notification.class.ChatMessage,
|
||||
components: [
|
||||
{
|
||||
kind: 'footer',
|
||||
component: chunter.component.ReactionsPresenter
|
||||
},
|
||||
{
|
||||
kind: 'action',
|
||||
component: chunter.component.ActivityMessageReactionsAction
|
||||
}
|
||||
]
|
||||
},
|
||||
chunter.ids.PinExtension
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
activity.class.ActivityExtension,
|
||||
core.space.Model,
|
||||
{
|
||||
ofClass: chunter.class.Backlink,
|
||||
isMention: true
|
||||
},
|
||||
chunter.ids.BackLinkActivityExtension
|
||||
chunter.ids.ChatMessageExtension
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
@@ -801,6 +813,55 @@ export function createModel (builder: Builder, options = { addApplication: true
|
||||
chunter.ids.ThreadNotification
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
notification.class.DocUpdateMessageViewlet,
|
||||
core.space.Model,
|
||||
{
|
||||
objectClass: chunter.class.Backlink,
|
||||
action: 'create',
|
||||
component: chunter.component.BacklinkContent,
|
||||
labelComponent: chunter.component.NotificationBacklinkLabel,
|
||||
hideIfRemoved: true
|
||||
},
|
||||
chunter.ids.NotificationBacklinkCreated
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
notification.class.DocUpdateMessageViewlet,
|
||||
core.space.Model,
|
||||
{
|
||||
objectClass: chunter.class.Backlink,
|
||||
action: 'remove',
|
||||
hideIfRemoved: true
|
||||
},
|
||||
chunter.ids.NotificationBacklinkRemoved
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
notification.class.DocUpdateMessageViewlet,
|
||||
core.space.Model,
|
||||
{
|
||||
objectClass: chunter.class.Reaction,
|
||||
action: 'create',
|
||||
component: chunter.component.NotificationReactionCreated,
|
||||
label: chunter.string.Reacted,
|
||||
onlyWithParent: true,
|
||||
hideIfRemoved: true
|
||||
},
|
||||
chunter.ids.NotificationReactionCreated
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
notification.class.DocUpdateMessageViewlet,
|
||||
core.space.Model,
|
||||
{
|
||||
objectClass: chunter.class.Reaction,
|
||||
action: 'remove',
|
||||
hideIfRemoved: true
|
||||
},
|
||||
chunter.ids.NotificationReactionRemoved
|
||||
)
|
||||
|
||||
createAction(builder, {
|
||||
...viewTemplates.open,
|
||||
target: chunter.class.Channel,
|
||||
|
||||
@@ -14,7 +14,17 @@
|
||||
//
|
||||
|
||||
import core, { TxOperations } from '@hcengineering/core'
|
||||
import { type MigrateOperation, type MigrationClient, type MigrationUpgradeClient } from '@hcengineering/model'
|
||||
import {
|
||||
type MigrateOperation,
|
||||
type MigrationClient,
|
||||
type MigrationUpgradeClient,
|
||||
tryMigrate
|
||||
} from '@hcengineering/model'
|
||||
import { chunterId } from '@hcengineering/chunter'
|
||||
import notification from '@hcengineering/notification'
|
||||
import { DOMAIN_NOTIFICATION } from '@hcengineering/model-notification'
|
||||
|
||||
import { DOMAIN_COMMENT } from './index'
|
||||
import chunter from './plugin'
|
||||
|
||||
export async function createGeneral (tx: TxOperations): Promise<void> {
|
||||
@@ -79,8 +89,20 @@ async function createBacklink (tx: TxOperations): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
async function convertCommentsToChatMessages (client: MigrationClient): Promise<void> {
|
||||
await client.update(DOMAIN_COMMENT, { _class: chunter.class.Comment }, { _class: notification.class.ChatMessage })
|
||||
await client.move(DOMAIN_COMMENT, { _class: notification.class.ChatMessage }, DOMAIN_NOTIFICATION)
|
||||
}
|
||||
|
||||
export const chunterOperation: MigrateOperation = {
|
||||
async migrate (client: MigrationClient): Promise<void> {},
|
||||
async migrate (client: MigrationClient): Promise<void> {
|
||||
await tryMigrate(client, chunterId, [
|
||||
{
|
||||
state: 'create-chat-messages',
|
||||
func: convertCommentsToChatMessages
|
||||
}
|
||||
])
|
||||
},
|
||||
async upgrade (client: MigrationUpgradeClient): Promise<void> {
|
||||
const tx = new TxOperations(client, core.account.System)
|
||||
await createGeneral(tx)
|
||||
|
||||
@@ -13,11 +13,16 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import type { ActivityExtension, DisplayTx, TxViewlet } from '@hcengineering/activity'
|
||||
import type { TxViewlet } from '@hcengineering/activity'
|
||||
import { chunterId, type Channel } from '@hcengineering/chunter'
|
||||
import chunter from '@hcengineering/chunter-resources/src/plugin'
|
||||
import type { Doc, Ref, Space } from '@hcengineering/core'
|
||||
import { type NotificationGroup } from '@hcengineering/notification'
|
||||
import {
|
||||
type ActivityMessage,
|
||||
type ActivityMessageExtension,
|
||||
type DocUpdateMessageViewlet,
|
||||
type NotificationGroup
|
||||
} from '@hcengineering/notification'
|
||||
import type { IntlString, Resource } from '@hcengineering/platform'
|
||||
import { mergeIds } from '@hcengineering/platform'
|
||||
import type { AnyComponent, Location } from '@hcengineering/ui/src/types'
|
||||
@@ -25,7 +30,6 @@ import type { Action, ActionCategory, ViewAction, ViewletDescriptor } from '@hce
|
||||
|
||||
export default mergeIds(chunterId, chunter, {
|
||||
component: {
|
||||
CommentPresenter: '' as AnyComponent,
|
||||
ChannelPresenter: '' as AnyComponent,
|
||||
DirectMessagePresenter: '' as AnyComponent,
|
||||
MessagePresenter: '' as AnyComponent,
|
||||
@@ -33,9 +37,11 @@ export default mergeIds(chunterId, chunter, {
|
||||
Threads: '' as AnyComponent,
|
||||
SavedMessages: '' as AnyComponent,
|
||||
ChunterBrowser: '' as AnyComponent,
|
||||
CommentReactions: '' as AnyComponent,
|
||||
ReactionsPresenter: '' as AnyComponent,
|
||||
ReactionsAction: '' as AnyComponent,
|
||||
PinComment: '' as AnyComponent
|
||||
ActivityMessageReactionsAction: '' as AnyComponent,
|
||||
BacklinkContent: '' as AnyComponent,
|
||||
BacklinkReference: '' as AnyComponent
|
||||
},
|
||||
action: {
|
||||
MarkCommentUnread: '' as Ref<Action>,
|
||||
@@ -74,15 +80,13 @@ export default mergeIds(chunterId, chunter, {
|
||||
PinnedMessages: '' as IntlString,
|
||||
SavedMessages: '' as IntlString,
|
||||
ThreadMessage: '' as IntlString,
|
||||
Reactions: '' as IntlString,
|
||||
Emoji: '' as IntlString,
|
||||
FilterComments: '' as IntlString,
|
||||
FilterPinnedComments: '' as IntlString,
|
||||
FilterBacklinks: '' as IntlString,
|
||||
DM: '' as IntlString,
|
||||
DMNotification: '' as IntlString,
|
||||
ConfigLabel: '' as IntlString,
|
||||
ConfigDescription: '' as IntlString
|
||||
ConfigDescription: '' as IntlString,
|
||||
Reacted: '' as IntlString
|
||||
},
|
||||
viewlet: {
|
||||
Chat: '' as Ref<ViewletDescriptor>
|
||||
@@ -93,15 +97,18 @@ export default mergeIds(chunterId, chunter, {
|
||||
TxCommentRemove: '' as Ref<TxViewlet>,
|
||||
TxBacklinkRemove: '' as Ref<TxViewlet>,
|
||||
TxMessageCreate: '' as Ref<TxViewlet>,
|
||||
TxChatMessageCreate: '' as Ref<TxViewlet>,
|
||||
TxChatMessageRemove: '' as Ref<TxViewlet>,
|
||||
ChunterNotificationGroup: '' as Ref<NotificationGroup>,
|
||||
ActivityExtension: '' as Ref<ActivityExtension>,
|
||||
BackLinkActivityExtension: '' as Ref<ActivityExtension>,
|
||||
PinExtension: '' as Ref<ActivityExtension>
|
||||
DocUpdateMessageExtension: '' as Ref<ActivityMessageExtension>,
|
||||
ChatMessageExtension: '' as Ref<ActivityMessageExtension>,
|
||||
NotificationBacklinkCreated: '' as Ref<DocUpdateMessageViewlet>,
|
||||
NotificationBacklinkRemoved: '' as Ref<DocUpdateMessageViewlet>,
|
||||
NotificationReactionCreated: '' as Ref<DocUpdateMessageViewlet>,
|
||||
NotificationReactionRemoved: '' as Ref<DocUpdateMessageViewlet>
|
||||
},
|
||||
activity: {
|
||||
TxCommentCreate: '' as AnyComponent,
|
||||
TxBacklinkCreate: '' as AnyComponent,
|
||||
TxBacklinkReference: '' as AnyComponent,
|
||||
TxMessageCreate: '' as AnyComponent
|
||||
},
|
||||
space: {
|
||||
@@ -114,8 +121,6 @@ export default mergeIds(chunterId, chunter, {
|
||||
GetFragment: '' as Resource<(doc: Doc, props: Record<string, any>) => Promise<Location>>
|
||||
},
|
||||
filter: {
|
||||
CommentsFilter: '' as Resource<(tx: DisplayTx, _class?: Ref<Doc>) => boolean>,
|
||||
PinnedCommentsFilter: '' as Resource<(tx: DisplayTx, _class?: Ref<Doc>) => boolean>,
|
||||
BacklinksFilter: '' as Resource<(tx: DisplayTx, _class?: Ref<Doc>) => boolean>
|
||||
BacklinksFilter: '' as Resource<(message: ActivityMessage, _class?: Ref<Doc>) => boolean>
|
||||
}
|
||||
})
|
||||
|
||||
+48
-16
@@ -112,7 +112,7 @@ export class TContact extends TDoc implements Contact {
|
||||
@Prop(Collection(attachment.class.Attachment), attachment.string.Attachments, { shortLabel: attachment.string.Files })
|
||||
attachments?: number
|
||||
|
||||
@Prop(Collection(chunter.class.Comment), chunter.string.Comments)
|
||||
@Prop(Collection(notification.class.ChatMessage), notification.string.Comments)
|
||||
comments?: number
|
||||
|
||||
@Prop(TypeString(), contact.string.Location)
|
||||
@@ -221,6 +221,26 @@ export function createModel (builder: Builder): void {
|
||||
TContactsTab
|
||||
)
|
||||
|
||||
builder.mixin(contact.class.Contact, core.class.Class, notification.mixin.ActivityDoc, {
|
||||
ignoreCollections: ['comments']
|
||||
})
|
||||
builder.mixin(contact.class.Person, core.class.Class, notification.mixin.ActivityDoc, {
|
||||
ignoreCollections: ['comments']
|
||||
})
|
||||
builder.mixin(contact.mixin.Employee, core.class.Class, notification.mixin.ActivityDoc, {
|
||||
ignoreCollections: ['comments']
|
||||
})
|
||||
builder.mixin(contact.class.Organization, core.class.Class, notification.mixin.ActivityDoc, {
|
||||
ignoreCollections: ['comments']
|
||||
})
|
||||
|
||||
builder.mixin(contact.class.Person, core.class.Class, notification.mixin.NotificationObjectPreposition, {
|
||||
preposition: contact.string.For
|
||||
})
|
||||
builder.mixin(contact.mixin.Employee, core.class.Class, notification.mixin.NotificationObjectPreposition, {
|
||||
preposition: contact.string.For
|
||||
})
|
||||
|
||||
builder.mixin(contact.mixin.Employee, core.class.Class, view.mixin.ObjectFactory, {
|
||||
component: contact.component.CreateEmployee
|
||||
})
|
||||
@@ -309,6 +329,16 @@ export function createModel (builder: Builder): void {
|
||||
match: { 'operations.name': { $exists: true } }
|
||||
})
|
||||
|
||||
builder.createDoc(notification.class.DocUpdateMessageViewlet, core.space.Model, {
|
||||
objectClass: contact.class.Person,
|
||||
action: 'update',
|
||||
config: {
|
||||
name: {
|
||||
presenter: contact.notification.NotificationNameChanged
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
builder.createDoc<Viewlet>(
|
||||
view.class.Viewlet,
|
||||
core.space.Model,
|
||||
@@ -938,32 +968,34 @@ export function createModel (builder: Builder): void {
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
activity.class.ActivityExtension,
|
||||
notification.class.ChatMessageViewlet,
|
||||
core.space.Model,
|
||||
{
|
||||
ofClass: contact.class.Person,
|
||||
components: {
|
||||
input: chunter.component.CommentInput
|
||||
}
|
||||
objectClass: contact.class.Person,
|
||||
label: chunter.string.LeftComment
|
||||
},
|
||||
contact.ids.PersonActivityExtension
|
||||
contact.ids.PersonChatMessageViewlet
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
activity.class.ActivityExtension,
|
||||
notification.class.ChatMessageViewlet,
|
||||
core.space.Model,
|
||||
{
|
||||
ofClass: contact.class.Organization,
|
||||
components: {
|
||||
input: chunter.component.CommentInput
|
||||
}
|
||||
objectClass: contact.mixin.Employee,
|
||||
label: chunter.string.LeftComment
|
||||
},
|
||||
contact.ids.OrganizationActivityExtension
|
||||
contact.ids.EmployeeChatMessageViewlet
|
||||
)
|
||||
|
||||
builder.mixin(contact.class.Contact, core.class.Class, activity.mixin.ExtraActivityComponent, {
|
||||
component: contact.component.ActivityChannelMessage
|
||||
})
|
||||
builder.createDoc(
|
||||
notification.class.ChatMessageViewlet,
|
||||
core.space.Model,
|
||||
{
|
||||
objectClass: contact.class.Organization,
|
||||
label: chunter.string.LeftComment
|
||||
},
|
||||
contact.ids.OrganizationChatMessageViewlet
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
notification.class.NotificationGroup,
|
||||
|
||||
@@ -17,19 +17,20 @@
|
||||
import { contactId } from '@hcengineering/contact'
|
||||
import contact from '@hcengineering/contact-resources/src/plugin'
|
||||
import type { Client, Doc, Ref } from '@hcengineering/core'
|
||||
import {} from '@hcengineering/core'
|
||||
import { type ObjectSearchCategory, type ObjectSearchFactory } from '@hcengineering/model-presentation'
|
||||
import { type NotificationGroup } from '@hcengineering/notification'
|
||||
import { type ChatMessageViewlet, type NotificationGroup } from '@hcengineering/notification'
|
||||
import { type IntlString, mergeIds, type Resource } from '@hcengineering/platform'
|
||||
import { type TemplateFieldFunc } from '@hcengineering/templates'
|
||||
import type { AnyComponent } from '@hcengineering/ui/src/types'
|
||||
import { type Action, type ActionCategory, type ViewAction } from '@hcengineering/view'
|
||||
import { type ActivityExtension } from '@hcengineering/activity'
|
||||
|
||||
export default mergeIds(contactId, contact, {
|
||||
activity: {
|
||||
TxNameChange: '' as AnyComponent
|
||||
},
|
||||
notification: {
|
||||
NotificationNameChanged: '' as AnyComponent
|
||||
},
|
||||
component: {
|
||||
PersonPresenter: '' as AnyComponent,
|
||||
ContactRefPresenter: '' as AnyComponent,
|
||||
@@ -57,7 +58,6 @@ export default mergeIds(contactId, contact, {
|
||||
AccountArrayEditor: '' as AnyComponent,
|
||||
ChannelFilter: '' as AnyComponent,
|
||||
MergePersons: '' as AnyComponent,
|
||||
ActivityChannelMessage: '' as AnyComponent,
|
||||
ChannelPanel: '' as AnyComponent,
|
||||
ActivityChannelPresenter: '' as AnyComponent,
|
||||
EmployeeFilter: '' as AnyComponent,
|
||||
@@ -116,8 +116,9 @@ export default mergeIds(contactId, contact, {
|
||||
ids: {
|
||||
OrganizationNotificationGroup: '' as Ref<NotificationGroup>,
|
||||
PersonNotificationGroup: '' as Ref<NotificationGroup>,
|
||||
PersonActivityExtension: '' as Ref<ActivityExtension>,
|
||||
OrganizationActivityExtension: '' as Ref<ActivityExtension>
|
||||
OrganizationChatMessageViewlet: '' as Ref<ChatMessageViewlet>,
|
||||
PersonChatMessageViewlet: '' as Ref<ChatMessageViewlet>,
|
||||
EmployeeChatMessageViewlet: '' as Ref<ChatMessageViewlet>
|
||||
},
|
||||
action: {
|
||||
KickEmployee: '' as Ref<Action>,
|
||||
@@ -135,6 +136,6 @@ export default mergeIds(contactId, contact, {
|
||||
GetContactName: '' as Resource<TemplateFieldFunc>,
|
||||
GetContactFirstName: '' as Resource<TemplateFieldFunc>,
|
||||
GetContactLastName: '' as Resource<TemplateFieldFunc>,
|
||||
ContactTitleProvider: '' as Resource<(client: Client, ref: Ref<Doc>) => Promise<string>>
|
||||
ContactTitleProvider: '' as Resource<(client: Client, ref: Ref<Doc>, doc?: Doc) => Promise<string>>
|
||||
}
|
||||
})
|
||||
|
||||
@@ -35,7 +35,6 @@
|
||||
"@hcengineering/model-view": "^0.6.0",
|
||||
"@hcengineering/model-workbench": "^0.6.1",
|
||||
"@hcengineering/model-contact": "^0.6.1",
|
||||
"@hcengineering/model-chunter": "^0.6.0",
|
||||
"@hcengineering/model-calendar": "^0.6.0",
|
||||
"@hcengineering/model-attachment": "^0.6.0",
|
||||
"@hcengineering/hr": "^0.6.12",
|
||||
|
||||
@@ -51,7 +51,6 @@ import {
|
||||
} from '@hcengineering/model'
|
||||
import attachment from '@hcengineering/model-attachment'
|
||||
import calendar from '@hcengineering/model-calendar'
|
||||
import chunter from '@hcengineering/model-chunter'
|
||||
import contact, { TEmployee, TPersonAccount } from '@hcengineering/model-contact'
|
||||
import core, { TAttachedDoc, TDoc, TSpace, TType } from '@hcengineering/model-core'
|
||||
import view, { classPresenter, createAction } from '@hcengineering/model-view'
|
||||
@@ -84,7 +83,7 @@ export class TDepartment extends TSpace implements Department {
|
||||
@Prop(Collection(attachment.class.Attachment), attachment.string.Attachments, { shortLabel: attachment.string.Files })
|
||||
attachments?: number
|
||||
|
||||
@Prop(Collection(chunter.class.Comment), chunter.string.Comments)
|
||||
@Prop(Collection(notification.class.ChatMessage), notification.string.Comments)
|
||||
comments?: number
|
||||
|
||||
avatar?: string | null
|
||||
@@ -158,7 +157,7 @@ export class TRequest extends TAttachedDoc implements Request {
|
||||
@Hidden()
|
||||
type!: Ref<RequestType>
|
||||
|
||||
@Prop(Collection(chunter.class.Comment), chunter.string.Comments)
|
||||
@Prop(Collection(notification.class.ChatMessage), notification.string.Comments)
|
||||
comments?: number
|
||||
|
||||
@Prop(Collection(attachment.class.Attachment), attachment.string.Attachments, { shortLabel: attachment.string.Files })
|
||||
|
||||
@@ -25,7 +25,6 @@
|
||||
"prettier-plugin-svelte": "^3.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hcengineering/activity": "^0.6.0",
|
||||
"@hcengineering/core": "^0.6.28",
|
||||
"@hcengineering/model": "^0.6.7",
|
||||
"@hcengineering/model-workbench": "^0.6.1",
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
//
|
||||
|
||||
import { type Domain, IndexKind, type Ref } from '@hcengineering/core'
|
||||
import activity from '@hcengineering/activity'
|
||||
import { type Category, type Product, type Variant, inventoryId } from '@hcengineering/inventory'
|
||||
import { type Builder, Collection, Index, Model, Prop, TypeRef, TypeString, UX } from '@hcengineering/model'
|
||||
import attachment from '@hcengineering/model-attachment'
|
||||
@@ -23,9 +22,10 @@ import { createAction } from '@hcengineering/model-view'
|
||||
import workbench from '@hcengineering/model-workbench'
|
||||
import setting from '@hcengineering/setting'
|
||||
import view, { type Viewlet } from '@hcengineering/view'
|
||||
import notification from '@hcengineering/notification'
|
||||
import chunter from '@hcengineering/model-chunter'
|
||||
import inventory from './plugin'
|
||||
|
||||
import inventory from './plugin'
|
||||
export { inventoryId } from '@hcengineering/inventory'
|
||||
export { inventoryOperation } from './migration'
|
||||
export { default } from './plugin'
|
||||
@@ -79,6 +79,10 @@ export class TVariant extends TAttachedDoc implements Variant {
|
||||
export function createModel (builder: Builder): void {
|
||||
builder.createModel(TCategory, TProduct, TVariant)
|
||||
|
||||
builder.mixin(inventory.class.Product, core.class.Class, notification.mixin.ActivityDoc, {})
|
||||
builder.mixin(inventory.class.Category, core.class.Class, notification.mixin.ActivityDoc, {})
|
||||
builder.mixin(inventory.class.Variant, core.class.Class, notification.mixin.ActivityDoc, {})
|
||||
|
||||
builder.mixin(inventory.class.Category, core.class.Class, view.mixin.ObjectPresenter, {
|
||||
presenter: inventory.component.CategoryPresenter
|
||||
})
|
||||
@@ -165,27 +169,23 @@ export function createModel (builder: Builder): void {
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
activity.class.ActivityExtension,
|
||||
notification.class.ChatMessageViewlet,
|
||||
core.space.Model,
|
||||
{
|
||||
ofClass: inventory.class.Product,
|
||||
components: {
|
||||
input: chunter.component.CommentInput
|
||||
}
|
||||
objectClass: inventory.class.Product,
|
||||
label: chunter.string.LeftComment
|
||||
},
|
||||
inventory.ids.ProductActivityExtension
|
||||
inventory.ids.ProductChatMessageViewlet
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
activity.class.ActivityExtension,
|
||||
notification.class.ChatMessageViewlet,
|
||||
core.space.Model,
|
||||
{
|
||||
ofClass: inventory.class.Category,
|
||||
components: {
|
||||
input: chunter.component.CommentInput
|
||||
}
|
||||
objectClass: inventory.class.Category,
|
||||
label: chunter.string.LeftComment
|
||||
},
|
||||
inventory.ids.CategoryActivityExtension
|
||||
inventory.ids.CategoryChatMessageViewlet
|
||||
)
|
||||
|
||||
createAction(builder, {
|
||||
|
||||
@@ -20,8 +20,7 @@ import inventory from '@hcengineering/inventory-resources/src/plugin'
|
||||
import { type IntlString, mergeIds } from '@hcengineering/platform'
|
||||
import type { AnyComponent } from '@hcengineering/ui/src/types'
|
||||
import { type Action, type ActionCategory, type ViewAction, type Viewlet } from '@hcengineering/view'
|
||||
import { type ActivityExtension } from '@hcengineering/activity'
|
||||
|
||||
import { type ChatMessageViewlet } from '@hcengineering/notification'
|
||||
export default mergeIds(inventoryId, inventory, {
|
||||
action: {
|
||||
CreateSubcategory: '' as Ref<Action>
|
||||
@@ -50,7 +49,7 @@ export default mergeIds(inventoryId, inventory, {
|
||||
ConfigDescription: '' as IntlString
|
||||
},
|
||||
ids: {
|
||||
ProductActivityExtension: '' as Ref<ActivityExtension>,
|
||||
CategoryActivityExtension: '' as Ref<ActivityExtension>
|
||||
ProductChatMessageViewlet: '' as Ref<ChatMessageViewlet>,
|
||||
CategoryChatMessageViewlet: '' as Ref<ChatMessageViewlet>
|
||||
}
|
||||
})
|
||||
|
||||
@@ -25,7 +25,6 @@
|
||||
"prettier-plugin-svelte": "^3.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hcengineering/activity": "^0.6.0",
|
||||
"@hcengineering/core": "^0.6.28",
|
||||
"@hcengineering/model": "^0.6.7",
|
||||
"@hcengineering/ui": "^0.6.11",
|
||||
|
||||
@@ -43,7 +43,6 @@ import workbench from '@hcengineering/model-workbench'
|
||||
import notification from '@hcengineering/notification'
|
||||
import setting from '@hcengineering/setting'
|
||||
import { type ViewOptionsModel } from '@hcengineering/view'
|
||||
import activity from '@hcengineering/activity'
|
||||
import lead from './plugin'
|
||||
|
||||
export { leadId } from '@hcengineering/lead'
|
||||
@@ -60,7 +59,7 @@ export class TFunnel extends TProject implements Funnel {
|
||||
@Prop(Collection(attachment.class.Attachment), attachment.string.Attachments, { shortLabel: attachment.string.Files })
|
||||
attachments?: number
|
||||
|
||||
@Prop(Collection(chunter.class.Comment), chunter.string.Comments)
|
||||
@Prop(Collection(notification.class.ChatMessage), notification.string.Comments)
|
||||
comments?: number
|
||||
}
|
||||
|
||||
@@ -103,6 +102,14 @@ export function createModel (builder: Builder): void {
|
||||
|
||||
builder.createModel(TFunnel, TLead, TCustomer)
|
||||
|
||||
builder.mixin(lead.class.Lead, core.class.Class, notification.mixin.ActivityDoc, {
|
||||
ignoreCollections: ['comments']
|
||||
})
|
||||
builder.mixin(lead.mixin.Customer, core.class.Class, notification.mixin.ActivityDoc, {
|
||||
ignoreCollections: ['comments']
|
||||
})
|
||||
builder.mixin(lead.class.Funnel, core.class.Class, notification.mixin.ActivityDoc, {})
|
||||
|
||||
builder.mixin(lead.class.Funnel, core.class.Class, workbench.mixin.SpaceView, {
|
||||
view: {
|
||||
class: lead.class.Lead,
|
||||
@@ -511,15 +518,13 @@ export function createModel (builder: Builder): void {
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
activity.class.ActivityExtension,
|
||||
notification.class.ChatMessageViewlet,
|
||||
core.space.Model,
|
||||
{
|
||||
ofClass: lead.class.Lead,
|
||||
components: {
|
||||
input: chunter.component.CommentInput
|
||||
}
|
||||
objectClass: lead.class.Lead,
|
||||
label: chunter.string.LeftComment
|
||||
},
|
||||
lead.ids.LeadActivityExtension
|
||||
lead.ids.LeadChatMessageViewlet
|
||||
)
|
||||
|
||||
builder.mixin(lead.class.Lead, core.class.Class, task.mixin.KanbanCard, {
|
||||
|
||||
@@ -17,13 +17,12 @@
|
||||
import type { Ref } from '@hcengineering/core'
|
||||
import { type Funnel, leadId } from '@hcengineering/lead'
|
||||
import lead from '@hcengineering/lead-resources/src/plugin'
|
||||
import { type NotificationGroup, type NotificationType } from '@hcengineering/notification'
|
||||
import { type ChatMessageViewlet, type NotificationGroup, type NotificationType } from '@hcengineering/notification'
|
||||
import type { IntlString } from '@hcengineering/platform'
|
||||
import { mergeIds } from '@hcengineering/platform'
|
||||
import { type ProjectType } from '@hcengineering/task'
|
||||
import type { AnyComponent } from '@hcengineering/ui/src/types'
|
||||
import { type Action, type ActionCategory, type Viewlet } from '@hcengineering/view'
|
||||
import { type ActivityExtension } from '@hcengineering/activity'
|
||||
|
||||
export default mergeIds(leadId, lead, {
|
||||
string: {
|
||||
@@ -71,6 +70,6 @@ export default mergeIds(leadId, lead, {
|
||||
FunnelNotificationGroup: '' as Ref<NotificationGroup>,
|
||||
LeadCreateNotification: '' as Ref<NotificationType>,
|
||||
AssigneeNotification: '' as Ref<NotificationType>,
|
||||
LeadActivityExtension: '' as Ref<ActivityExtension>
|
||||
LeadChatMessageViewlet: '' as Ref<ChatMessageViewlet>
|
||||
}
|
||||
})
|
||||
|
||||
@@ -15,29 +15,30 @@
|
||||
"devDependencies": {
|
||||
"@hcengineering/platform-rig": "^0.6.0",
|
||||
"@typescript-eslint/eslint-plugin": "^6.11.0",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-promise": "^6.1.1",
|
||||
"eslint-plugin-n": "^15.4.0",
|
||||
"eslint": "^8.54.0",
|
||||
"@typescript-eslint/parser": "^6.11.0",
|
||||
"eslint": "^8.54.0",
|
||||
"eslint-config-standard-with-typescript": "^40.0.0",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-n": "^15.4.0",
|
||||
"eslint-plugin-promise": "^6.1.1",
|
||||
"prettier": "^3.1.0",
|
||||
"prettier-plugin-svelte": "^3.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hcengineering/activity": "^0.6.0",
|
||||
"@hcengineering/chunter": "^0.6.12",
|
||||
"@hcengineering/core": "^0.6.28",
|
||||
"@hcengineering/model": "^0.6.7",
|
||||
"@hcengineering/ui": "^0.6.11",
|
||||
"@hcengineering/platform": "^0.6.9",
|
||||
"@hcengineering/model-attachment": "^0.6.0",
|
||||
"@hcengineering/model-core": "^0.6.0",
|
||||
"@hcengineering/model-preference": "^0.6.0",
|
||||
"@hcengineering/model-view": "^0.6.0",
|
||||
"@hcengineering/activity": "^0.6.0",
|
||||
"@hcengineering/view": "^0.6.9",
|
||||
"@hcengineering/workbench": "^0.6.9",
|
||||
"@hcengineering/model-workbench": "^0.6.1",
|
||||
"@hcengineering/notification": "^0.6.16",
|
||||
"@hcengineering/platform": "^0.6.9",
|
||||
"@hcengineering/setting": "^0.6.11",
|
||||
"@hcengineering/chunter": "^0.6.12"
|
||||
"@hcengineering/ui": "^0.6.11",
|
||||
"@hcengineering/view": "^0.6.9",
|
||||
"@hcengineering/workbench": "^0.6.9"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
import { type Builder } from '@hcengineering/model'
|
||||
import view, { createAction } from '@hcengineering/model-view'
|
||||
import core from '@hcengineering/model-core'
|
||||
|
||||
import notification from './plugin'
|
||||
|
||||
export function buildActivityMessages (builder: Builder): void {
|
||||
builder.mixin(notification.class.DocUpdateMessage, core.class.Class, notification.mixin.ActivityDoc, {})
|
||||
builder.mixin(notification.class.ChatMessage, core.class.Class, notification.mixin.ActivityDoc, {})
|
||||
|
||||
builder.mixin(notification.class.ChatMessage, core.class.Class, view.mixin.CollectionPresenter, {
|
||||
presenter: notification.component.ChatMessagesPresenter
|
||||
})
|
||||
|
||||
builder.mixin(notification.class.ChatMessage, core.class.Class, view.mixin.ObjectPresenter, {
|
||||
presenter: notification.component.ChatMessagePresenter
|
||||
})
|
||||
|
||||
builder.mixin(notification.class.DocUpdateMessage, core.class.Class, view.mixin.ObjectPresenter, {
|
||||
presenter: notification.component.DocUpdateMessagePresenter
|
||||
})
|
||||
|
||||
builder.createDoc(
|
||||
notification.class.ActivityMessageExtension,
|
||||
core.space.Model,
|
||||
{
|
||||
ofMessage: notification.class.DocUpdateMessage,
|
||||
components: [
|
||||
{
|
||||
kind: 'action',
|
||||
component: notification.component.PinMessageAction
|
||||
}
|
||||
]
|
||||
},
|
||||
notification.ids.DocUpdateMessagePinExtension
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
notification.class.ActivityMessageExtension,
|
||||
core.space.Model,
|
||||
{
|
||||
ofMessage: notification.class.ChatMessage,
|
||||
components: [
|
||||
{
|
||||
kind: 'action',
|
||||
component: notification.component.PinMessageAction
|
||||
}
|
||||
]
|
||||
},
|
||||
notification.ids.ChatMessagePinExtension
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
notification.class.DocUpdateMessageViewlet,
|
||||
core.space.Model,
|
||||
{
|
||||
objectClass: notification.mixin.Collaborators,
|
||||
action: 'update',
|
||||
icon: notification.icon.Notifications,
|
||||
component: notification.activity.TxCollaboratorsChange,
|
||||
label: notification.string.ChangeCollaborators
|
||||
},
|
||||
notification.ids.NotificationCollaboratorsChanged
|
||||
)
|
||||
|
||||
builder.mixin(notification.mixin.Collaborators, core.class.Class, notification.mixin.NotificationAttributePresenter, {
|
||||
presenter: notification.component.NotificationCollaboratorsChanged
|
||||
})
|
||||
|
||||
builder.createDoc(notification.class.ActivityMessagesFilter, core.space.Model, {
|
||||
label: notification.string.Attributes,
|
||||
filter: notification.filter.AttributesFilter
|
||||
})
|
||||
|
||||
builder.createDoc(notification.class.ActivityMessagesFilter, core.space.Model, {
|
||||
label: notification.string.Pinned,
|
||||
filter: notification.filter.PinnedFilter
|
||||
})
|
||||
|
||||
builder.createDoc(notification.class.ActivityMessagesFilter, core.space.Model, {
|
||||
label: notification.string.Comments,
|
||||
filter: notification.filter.ChatMessagesFilter
|
||||
})
|
||||
|
||||
buildActions(builder)
|
||||
}
|
||||
|
||||
function buildActions (builder: Builder): void {
|
||||
createAction(
|
||||
builder,
|
||||
{
|
||||
action: notification.actionImpl.DeleteChatMessage,
|
||||
label: view.string.Delete,
|
||||
icon: view.icon.Delete,
|
||||
input: 'focus',
|
||||
keyBinding: ['Backspace'],
|
||||
category: notification.category.Notification,
|
||||
target: notification.class.ChatMessage,
|
||||
context: { mode: ['context', 'browser'], group: 'edit' }
|
||||
},
|
||||
notification.action.DeleteChatMessage
|
||||
)
|
||||
|
||||
createAction(
|
||||
builder,
|
||||
{
|
||||
action: notification.actionImpl.MarkAsReadInboxNotification,
|
||||
label: notification.string.MarkAsRead,
|
||||
icon: notification.icon.Notifications,
|
||||
input: 'focus',
|
||||
visibilityTester: notification.function.HasInboxNotifications,
|
||||
category: notification.category.Notification,
|
||||
target: notification.class.ActivityMessage,
|
||||
context: { mode: 'context', application: notification.app.Notification, group: 'edit' }
|
||||
},
|
||||
notification.action.MarkAsReadInboxNotification
|
||||
)
|
||||
|
||||
createAction(
|
||||
builder,
|
||||
{
|
||||
action: notification.actionImpl.MarkAsUnreadInboxNotification,
|
||||
label: notification.string.MarkAsUnread,
|
||||
icon: notification.icon.Track,
|
||||
input: 'focus',
|
||||
visibilityTester: notification.function.HasntInboxNotifications,
|
||||
category: notification.category.Notification,
|
||||
target: notification.class.ActivityMessage,
|
||||
context: { mode: 'context', application: notification.app.Notification, group: 'edit' }
|
||||
},
|
||||
notification.action.MarkAsUnreadInboxNotification
|
||||
)
|
||||
|
||||
createAction(
|
||||
builder,
|
||||
{
|
||||
action: notification.actionImpl.DeleteInboxNotification,
|
||||
label: notification.string.Archive,
|
||||
icon: view.icon.Archive,
|
||||
input: 'focus',
|
||||
keyBinding: ['Backspace'],
|
||||
category: notification.category.Notification,
|
||||
target: notification.class.ActivityMessage,
|
||||
context: { mode: ['context', 'browser'], group: 'edit' }
|
||||
},
|
||||
notification.action.DeleteInboxNotification
|
||||
)
|
||||
}
|
||||
@@ -17,9 +17,6 @@
|
||||
import activity from '@hcengineering/activity'
|
||||
import chunter from '@hcengineering/chunter'
|
||||
import {
|
||||
DOMAIN_MODEL,
|
||||
Hierarchy,
|
||||
IndexKind,
|
||||
type Account,
|
||||
type AttachedDoc,
|
||||
type Class,
|
||||
@@ -27,20 +24,49 @@ import {
|
||||
type Data,
|
||||
type Doc,
|
||||
type Domain,
|
||||
DOMAIN_MODEL,
|
||||
Hierarchy,
|
||||
IndexKind,
|
||||
type Ref,
|
||||
type Timestamp,
|
||||
type Tx,
|
||||
type TxCUD
|
||||
} from '@hcengineering/core'
|
||||
import { ArrOf, Index, Mixin, Model, Prop, TypeRef, TypeString, UX, type Builder } from '@hcengineering/model'
|
||||
import {
|
||||
ArrOf,
|
||||
type Builder,
|
||||
Index,
|
||||
Mixin,
|
||||
Model,
|
||||
Prop,
|
||||
TypeMarkup,
|
||||
TypeRef,
|
||||
TypeString,
|
||||
UX,
|
||||
Collection as PropCollection,
|
||||
TypeBoolean,
|
||||
TypeDate
|
||||
} from '@hcengineering/model'
|
||||
import core, { TAttachedDoc, TClass, TDoc } from '@hcengineering/model-core'
|
||||
import preference, { TPreference } from '@hcengineering/model-preference'
|
||||
import view, { createAction } from '@hcengineering/model-view'
|
||||
import workbench from '@hcengineering/model-workbench'
|
||||
import {
|
||||
notificationId,
|
||||
type DocUpdateTx,
|
||||
type ActivityMessage,
|
||||
type ActivityMessageExtension,
|
||||
type ActivityMessageExtensionKind,
|
||||
type ChatMessage,
|
||||
type ChatMessageViewlet,
|
||||
type DocUpdateAction,
|
||||
type DocAttributeUpdates,
|
||||
type DocUpdateMessage,
|
||||
type DocUpdateMessageViewlet,
|
||||
type DocUpdateMessageViewletAttributesConfig,
|
||||
type DocUpdates,
|
||||
type DocUpdateTx,
|
||||
inboxId,
|
||||
type InboxNotification,
|
||||
type DocNotifyContext,
|
||||
type Notification,
|
||||
type NotificationGroup,
|
||||
type NotificationObjectPresenter,
|
||||
@@ -50,12 +76,21 @@ import {
|
||||
type NotificationSetting,
|
||||
type NotificationStatus,
|
||||
type NotificationTemplate,
|
||||
type NotificationType
|
||||
type NotificationType,
|
||||
type ActivityMessagesFilter,
|
||||
type ActivityDoc,
|
||||
type NotificationObjectPreposition,
|
||||
notificationId
|
||||
} from '@hcengineering/notification'
|
||||
import type { Asset, IntlString } from '@hcengineering/platform'
|
||||
import { type Asset, type IntlString, type Resource } from '@hcengineering/platform'
|
||||
import setting from '@hcengineering/setting'
|
||||
import { type AnyComponent } from '@hcengineering/ui/src/types'
|
||||
import attachment from '@hcengineering/model-attachment'
|
||||
import { type NotificationAttributePresenter } from '@hcengineering/view'
|
||||
|
||||
import notification from './plugin'
|
||||
import { buildActivityMessages } from './activityMessages'
|
||||
|
||||
export { notificationId } from '@hcengineering/notification'
|
||||
export { notificationOperation } from './migration'
|
||||
export { notification as default }
|
||||
@@ -139,6 +174,21 @@ export class TNotificationPreview extends TClass implements NotificationPreview
|
||||
presenter!: AnyComponent
|
||||
}
|
||||
|
||||
@Mixin(notification.mixin.ActivityDoc, core.class.Class)
|
||||
export class TActivityDoc extends TClass implements ActivityDoc {
|
||||
ignoreCollections?: string[]
|
||||
}
|
||||
|
||||
@Mixin(notification.mixin.NotificationObjectPreposition, core.class.Class)
|
||||
export class TNotificationObjectPreposition extends TClass implements NotificationObjectPreposition {
|
||||
preposition!: IntlString
|
||||
}
|
||||
|
||||
@Mixin(notification.mixin.NotificationAttributePresenter, core.class.Class)
|
||||
export class TNotificationAttributePresenter extends TClass implements NotificationAttributePresenter {
|
||||
presenter!: AnyComponent
|
||||
}
|
||||
|
||||
@Model(notification.class.DocUpdates, core.class.Doc, DOMAIN_NOTIFICATION)
|
||||
export class TDocUpdates extends TDoc implements DocUpdates {
|
||||
@Index(IndexKind.Indexed)
|
||||
@@ -155,6 +205,149 @@ export class TDocUpdates extends TDoc implements DocUpdates {
|
||||
txes!: DocUpdateTx[]
|
||||
}
|
||||
|
||||
@Model(notification.class.DocNotifyContext, core.class.Doc, DOMAIN_NOTIFICATION)
|
||||
export class TDocNotifyContext extends TDoc implements DocNotifyContext {
|
||||
@Prop(TypeRef(core.class.Account), core.string.Account)
|
||||
@Index(IndexKind.Indexed)
|
||||
user!: Ref<Account>
|
||||
|
||||
@Prop(TypeRef(core.class.Doc), core.string.AttachedTo)
|
||||
@Index(IndexKind.Indexed)
|
||||
attachedTo!: Ref<Doc>
|
||||
|
||||
@Prop(TypeRef(core.class.Class), core.string.AttachedToClass)
|
||||
@Index(IndexKind.Indexed)
|
||||
attachedToClass!: Ref<Class<Doc>>
|
||||
|
||||
@Prop(TypeBoolean(), core.string.Archived)
|
||||
@Index(IndexKind.Indexed)
|
||||
hidden!: boolean
|
||||
|
||||
@Prop(TypeDate(), core.string.Date)
|
||||
@Index(IndexKind.Indexed)
|
||||
lastViewedTimestamp?: Timestamp
|
||||
|
||||
@Prop(TypeDate(), core.string.Date)
|
||||
@Index(IndexKind.Indexed)
|
||||
lastUpdateTimestamp?: Timestamp
|
||||
}
|
||||
|
||||
@Model(notification.class.ActivityMessage, core.class.AttachedDoc, DOMAIN_NOTIFICATION)
|
||||
export class TActivityMessage extends TAttachedDoc implements ActivityMessage {
|
||||
@Prop(TypeBoolean(), notification.string.Pinned)
|
||||
@Index(IndexKind.Indexed)
|
||||
isPinned?: boolean
|
||||
|
||||
@Prop(PropCollection(chunter.class.Reaction), chunter.string.Reactions)
|
||||
reactions?: number
|
||||
}
|
||||
|
||||
@Model(notification.class.DocUpdateMessage, notification.class.ActivityMessage, DOMAIN_NOTIFICATION)
|
||||
export class TDocUpdateMessage extends TActivityMessage implements DocUpdateMessage {
|
||||
@Prop(TypeRef(core.class.Doc), core.string.Object)
|
||||
@Index(IndexKind.Indexed)
|
||||
objectId!: Ref<Doc>
|
||||
|
||||
@Prop(TypeRef(core.class.Class), core.string.Class)
|
||||
@Index(IndexKind.Indexed)
|
||||
objectClass!: Ref<Class<Doc>>
|
||||
|
||||
@Prop(TypeRef(core.class.TxCUD), core.string.Object)
|
||||
@Index(IndexKind.Indexed)
|
||||
txId!: Ref<TxCUD<Doc>>
|
||||
|
||||
action!: DocUpdateAction
|
||||
updateCollection?: string
|
||||
attributeUpdates?: DocAttributeUpdates
|
||||
}
|
||||
|
||||
@Model(notification.class.ChatMessage, notification.class.ActivityMessage, DOMAIN_NOTIFICATION)
|
||||
export class TChatMessage extends TActivityMessage implements ChatMessage {
|
||||
@Prop(TypeMarkup(), chunter.string.Message)
|
||||
@Index(IndexKind.FullText)
|
||||
message!: string
|
||||
|
||||
@Prop(PropCollection(attachment.class.Attachment), attachment.string.Attachments, {
|
||||
shortLabel: attachment.string.Files
|
||||
})
|
||||
attachments?: number
|
||||
|
||||
@Prop(TypeBoolean(), core.string.Boolean)
|
||||
@Index(IndexKind.Indexed)
|
||||
isEdited?: boolean
|
||||
}
|
||||
|
||||
@Model(notification.class.InboxNotification, core.class.Doc, DOMAIN_NOTIFICATION)
|
||||
export class TInboxNotification extends TDoc implements InboxNotification {
|
||||
@Prop(TypeRef(notification.class.ActivityMessage), core.string.AttachedTo)
|
||||
@Index(IndexKind.Indexed)
|
||||
attachedTo!: Ref<ActivityMessage>
|
||||
|
||||
@Prop(TypeRef(notification.class.ActivityMessage), core.string.AttachedToClass)
|
||||
@Index(IndexKind.Indexed)
|
||||
attachedToClass!: Ref<Class<ActivityMessage>>
|
||||
|
||||
@Prop(TypeRef(notification.class.DocNotifyContext), core.string.AttachedTo)
|
||||
@Index(IndexKind.Indexed)
|
||||
docNotifyContext!: Ref<DocNotifyContext>
|
||||
|
||||
@Prop(TypeRef(core.class.Account), core.string.Account)
|
||||
@Index(IndexKind.Indexed)
|
||||
user!: Ref<Account>
|
||||
|
||||
@Prop(TypeBoolean(), core.string.Boolean)
|
||||
@Index(IndexKind.Indexed)
|
||||
isViewed!: boolean
|
||||
}
|
||||
|
||||
@Model(notification.class.DocUpdateMessageViewlet, core.class.Doc, DOMAIN_MODEL)
|
||||
export class TDocUpdateMessageViewlet extends TDoc implements DocUpdateMessageViewlet {
|
||||
@Prop(TypeRef(core.class.Doc), core.string.Class)
|
||||
@Index(IndexKind.Indexed)
|
||||
objectClass!: Ref<Class<Doc>>
|
||||
|
||||
@Prop(TypeString(), core.string.String)
|
||||
@Index(IndexKind.Indexed)
|
||||
action!: DocUpdateAction
|
||||
|
||||
label?: IntlString
|
||||
labelComponent?: AnyComponent
|
||||
|
||||
valueAttr?: string
|
||||
|
||||
icon?: Asset
|
||||
component?: AnyComponent
|
||||
config?: DocUpdateMessageViewletAttributesConfig
|
||||
hideIfRemoved?: boolean
|
||||
onlyWithParent?: boolean
|
||||
}
|
||||
|
||||
@Model(notification.class.ChatMessageViewlet, core.class.Doc, DOMAIN_MODEL)
|
||||
export class TChatMessageViewlet extends TDoc implements ChatMessageViewlet {
|
||||
@Prop(TypeRef(core.class.Doc), core.string.Class)
|
||||
@Index(IndexKind.Indexed)
|
||||
objectClass!: Ref<Class<Doc>>
|
||||
|
||||
label?: IntlString
|
||||
hidden?: boolean
|
||||
onlyWithParent?: boolean
|
||||
}
|
||||
|
||||
@Model(notification.class.ActivityMessageExtension, core.class.Doc, DOMAIN_MODEL)
|
||||
export class TActivityMessageExtension extends TDoc implements ActivityMessageExtension {
|
||||
@Prop(TypeRef(notification.class.ActivityMessage), core.string.Class)
|
||||
@Index(IndexKind.Indexed)
|
||||
ofMessage!: Ref<Class<ActivityMessage>>
|
||||
|
||||
components!: { kind: ActivityMessageExtensionKind, component: AnyComponent }[]
|
||||
}
|
||||
|
||||
@Model(notification.class.ActivityMessagesFilter, core.class.Doc, DOMAIN_MODEL)
|
||||
export class TActivityMessagesFilter extends TDoc implements ActivityMessagesFilter {
|
||||
label!: IntlString
|
||||
filter!: Resource<(message: ActivityMessage, _class?: Ref<Doc>) => boolean>
|
||||
}
|
||||
|
||||
export function createModel (builder: Builder): void {
|
||||
builder.createModel(
|
||||
TNotification,
|
||||
@@ -167,7 +360,19 @@ export function createModel (builder: Builder): void {
|
||||
TCollaborators,
|
||||
TDocUpdates,
|
||||
TNotificationObjectPresenter,
|
||||
TNotificationPreview
|
||||
TNotificationPreview,
|
||||
TDocNotifyContext,
|
||||
TDocUpdateMessage,
|
||||
TChatMessage,
|
||||
TActivityMessage,
|
||||
TInboxNotification,
|
||||
TDocUpdateMessageViewlet,
|
||||
TActivityMessageExtension,
|
||||
TChatMessageViewlet,
|
||||
TActivityMessagesFilter,
|
||||
TActivityDoc,
|
||||
TNotificationObjectPreposition,
|
||||
TNotificationAttributePresenter
|
||||
)
|
||||
|
||||
// Temporarily disabled, we should think about it
|
||||
@@ -228,6 +433,45 @@ export function createModel (builder: Builder): void {
|
||||
notification.app.Notification
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
workbench.class.Application,
|
||||
core.space.Model,
|
||||
{
|
||||
label: notification.string.Inbox,
|
||||
icon: notification.icon.Notifications,
|
||||
alias: inboxId,
|
||||
hidden: true,
|
||||
locationResolver: notification.resolver.Location,
|
||||
navigatorModel: {
|
||||
aside: notification.component.InboxAside,
|
||||
spaces: [],
|
||||
specials: [
|
||||
{
|
||||
id: 'all',
|
||||
component: notification.component.NewInbox,
|
||||
icon: notification.icon.Activity,
|
||||
label: notification.string.AllActivity,
|
||||
componentProps: {
|
||||
type: 'all',
|
||||
label: notification.string.AllActivity
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'reactions',
|
||||
component: notification.component.NewInbox,
|
||||
icon: notification.icon.Emoji,
|
||||
label: notification.string.Reactions,
|
||||
componentProps: {
|
||||
_class: chunter.class.Reaction,
|
||||
label: notification.string.Reactions
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
notification.app.Inbox
|
||||
)
|
||||
|
||||
createAction(
|
||||
builder,
|
||||
{
|
||||
@@ -352,6 +596,8 @@ export function createModel (builder: Builder): void {
|
||||
},
|
||||
notification.ids.TxDmCreation
|
||||
)
|
||||
|
||||
buildActivityMessages(builder)
|
||||
}
|
||||
|
||||
export function generateClassNotificationTypes (
|
||||
|
||||
@@ -15,7 +15,11 @@
|
||||
//
|
||||
|
||||
import { type Doc, type Ref } from '@hcengineering/core'
|
||||
import notification, { notificationId } from '@hcengineering/notification'
|
||||
import notification, {
|
||||
type ActivityMessageExtension,
|
||||
type DocUpdateMessageViewlet,
|
||||
notificationId
|
||||
} from '@hcengineering/notification'
|
||||
import { type IntlString, type Resource, mergeIds } from '@hcengineering/platform'
|
||||
import { type AnyComponent } from '@hcengineering/ui/src/types'
|
||||
import { type Action, type ActionCategory, type ViewAction } from '@hcengineering/view'
|
||||
@@ -29,24 +33,39 @@ export default mergeIds(notificationId, notification, {
|
||||
EmailNotification: '' as IntlString,
|
||||
Collaborators: '' as IntlString,
|
||||
Archive: '' as IntlString,
|
||||
MarkAsUnread: '' as IntlString
|
||||
MarkAsUnread: '' as IntlString,
|
||||
MarkAsRead: '' as IntlString,
|
||||
ChangeCollaborators: '' as IntlString,
|
||||
AllActivity: '' as IntlString,
|
||||
Threads: '' as IntlString,
|
||||
Mentions: '' as IntlString,
|
||||
Reactions: '' as IntlString
|
||||
},
|
||||
app: {
|
||||
Notification: '' as Ref<Application>
|
||||
Notification: '' as Ref<Application>,
|
||||
Inbox: '' as Ref<Application>
|
||||
},
|
||||
activity: {
|
||||
TxCollaboratorsChange: '' as AnyComponent,
|
||||
TxDmCreation: '' as AnyComponent
|
||||
},
|
||||
ids: {
|
||||
TxCollaboratorsChange: '' as Ref<TxViewlet>,
|
||||
TxDmCreation: '' as Ref<TxViewlet>
|
||||
TxDmCreation: '' as Ref<TxViewlet>,
|
||||
NotificationCollaboratorsChanged: '' as Ref<DocUpdateMessageViewlet>,
|
||||
DocUpdateMessagePinExtension: '' as Ref<ActivityMessageExtension>,
|
||||
ChatMessagePinExtension: '' as Ref<ActivityMessageExtension>
|
||||
},
|
||||
component: {
|
||||
NotificationSettings: '' as AnyComponent
|
||||
NotificationSettings: '' as AnyComponent,
|
||||
InboxAside: '' as AnyComponent,
|
||||
ChatMessagePresenter: '' as AnyComponent,
|
||||
DocUpdateMessagePresenter: '' as AnyComponent,
|
||||
PinMessageAction: '' as AnyComponent
|
||||
},
|
||||
function: {
|
||||
HasntNotifications: '' as Resource<(doc?: Doc | Doc[]) => Promise<boolean>>
|
||||
HasntNotifications: '' as Resource<(doc?: Doc | Doc[]) => Promise<boolean>>,
|
||||
HasntInboxNotifications: '' as Resource<(doc?: Doc | Doc[]) => Promise<boolean>>,
|
||||
HasInboxNotifications: '' as Resource<(doc?: Doc | Doc[]) => Promise<boolean>>
|
||||
},
|
||||
category: {
|
||||
Notification: '' as Ref<ActionCategory>
|
||||
@@ -60,6 +79,10 @@ export default mergeIds(notificationId, notification, {
|
||||
actionImpl: {
|
||||
Unsubscribe: '' as ViewAction,
|
||||
Hide: '' as ViewAction,
|
||||
MarkAsUnread: '' as ViewAction
|
||||
MarkAsUnread: '' as ViewAction,
|
||||
MarkAsUnreadInboxNotification: '' as ViewAction,
|
||||
MarkAsReadInboxNotification: '' as ViewAction,
|
||||
DeleteInboxNotification: '' as ViewAction,
|
||||
DeleteChatMessage: '' as ViewAction
|
||||
}
|
||||
})
|
||||
|
||||
+17
-19
@@ -25,32 +25,30 @@
|
||||
"prettier-plugin-svelte": "^3.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hcengineering/activity": "^0.6.0",
|
||||
"@anticrm/skillset": "^0.6.0",
|
||||
"@hcengineering/contact": "^0.6.20",
|
||||
"@hcengineering/core": "^0.6.28",
|
||||
"@hcengineering/model": "^0.6.7",
|
||||
"@hcengineering/ui": "^0.6.11",
|
||||
"@hcengineering/contact": "^0.6.20",
|
||||
"@hcengineering/platform": "^0.6.9",
|
||||
"@hcengineering/model-attachment": "^0.6.0",
|
||||
"@hcengineering/model-calendar": "^0.6.0",
|
||||
"@hcengineering/model-chunter": "^0.6.0",
|
||||
"@hcengineering/model-contact": "^0.6.1",
|
||||
"@hcengineering/model-core": "^0.6.0",
|
||||
"@hcengineering/model-notification": "^0.6.0",
|
||||
"@hcengineering/model-presentation": "^0.6.0",
|
||||
"@hcengineering/model-tags": "^0.6.0",
|
||||
"@hcengineering/model-task": "^0.6.0",
|
||||
"@hcengineering/model-tracker": "^0.6.0",
|
||||
"@hcengineering/model-view": "^0.6.0",
|
||||
"@hcengineering/model-workbench": "^0.6.1",
|
||||
"@hcengineering/model-contact": "^0.6.1",
|
||||
"@hcengineering/notification": "^0.6.16",
|
||||
"@hcengineering/platform": "^0.6.9",
|
||||
"@hcengineering/recruit": "^0.6.21",
|
||||
"@hcengineering/recruit-resources": "^0.6.0",
|
||||
"@hcengineering/chunter": "^0.6.12",
|
||||
"@hcengineering/notification": "^0.6.16",
|
||||
"@hcengineering/model-attachment": "^0.6.0",
|
||||
"@hcengineering/model-chunter": "^0.6.0",
|
||||
"@hcengineering/view": "^0.6.9",
|
||||
"@hcengineering/task": "^0.6.13",
|
||||
"@hcengineering/setting": "^0.6.11",
|
||||
"@hcengineering/model-task": "^0.6.0",
|
||||
"@hcengineering/workbench": "^0.6.9",
|
||||
"@hcengineering/model-tracker": "^0.6.0",
|
||||
"@hcengineering/model-presentation": "^0.6.0",
|
||||
"@hcengineering/model-notification": "^0.6.0",
|
||||
"@hcengineering/model-calendar": "^0.6.0",
|
||||
"@hcengineering/model-tags": "^0.6.0",
|
||||
"@anticrm/skillset": "^0.6.0"
|
||||
"@hcengineering/task": "^0.6.13",
|
||||
"@hcengineering/ui": "^0.6.11",
|
||||
"@hcengineering/view": "^0.6.9",
|
||||
"@hcengineering/workbench": "^0.6.9"
|
||||
}
|
||||
}
|
||||
|
||||
+49
-24
@@ -56,7 +56,7 @@ import {
|
||||
} from '@hcengineering/recruit'
|
||||
import setting from '@hcengineering/setting'
|
||||
import { type KeyBinding, type ViewOptionModel, type ViewOptionsModel } from '@hcengineering/view'
|
||||
import activity from '@hcengineering/activity'
|
||||
|
||||
import recruit from './plugin'
|
||||
import { createReviewModel, reviewTableConfig, reviewTableOptions } from './review'
|
||||
import { TOpinion, TReview } from './review-model'
|
||||
@@ -85,7 +85,7 @@ export class TVacancy extends TProject implements Vacancy {
|
||||
@Prop(TypeRef(contact.class.Organization), recruit.string.Company, { icon: contact.icon.Company })
|
||||
company?: Ref<Organization>
|
||||
|
||||
@Prop(Collection(chunter.class.Comment), chunter.string.Comments)
|
||||
@Prop(Collection(notification.class.ChatMessage), notification.string.Comments)
|
||||
comments?: number
|
||||
|
||||
@Prop(TypeString(), recruit.string.Vacancy)
|
||||
@@ -197,6 +197,18 @@ export class TApplicantMatch extends TAttachedDoc implements ApplicantMatch {
|
||||
export function createModel (builder: Builder): void {
|
||||
builder.createModel(TVacancy, TCandidates, TCandidate, TApplicant, TReview, TOpinion, TVacancyList, TApplicantMatch)
|
||||
|
||||
builder.mixin(recruit.class.Vacancy, core.class.Class, notification.mixin.ActivityDoc, {
|
||||
ignoreCollections: ['comments']
|
||||
})
|
||||
builder.mixin(recruit.class.Applicant, core.class.Class, notification.mixin.ActivityDoc, {
|
||||
ignoreCollections: ['comments']
|
||||
})
|
||||
builder.mixin(recruit.class.Review, core.class.Class, notification.mixin.ActivityDoc, {})
|
||||
|
||||
builder.mixin(recruit.mixin.Candidate, core.class.Class, notification.mixin.ActivityDoc, {
|
||||
ignoreCollections: ['comments']
|
||||
})
|
||||
|
||||
builder.mixin(recruit.class.Vacancy, core.class.Class, workbench.mixin.SpaceView, {
|
||||
view: {
|
||||
class: recruit.class.Applicant,
|
||||
@@ -832,7 +844,7 @@ export function createModel (builder: Builder): void {
|
||||
key: 'title',
|
||||
props: { kind: 'list', size: 'small', shouldShowName: false }
|
||||
},
|
||||
{ key: 'comments', displayProps: { key: 'comments', suffix: true } },
|
||||
'comments',
|
||||
{ key: '', displayProps: { grow: true } },
|
||||
{
|
||||
key: '$lookup.channels',
|
||||
@@ -882,7 +894,7 @@ export function createModel (builder: Builder): void {
|
||||
label: recruit.string.Applications,
|
||||
props: { kind: 'list', size: 'small', shouldShowName: false }
|
||||
},
|
||||
{ key: 'comments', displayProps: { key: 'comments', suffix: true } },
|
||||
'comments',
|
||||
{
|
||||
key: '',
|
||||
presenter: tracker.component.RelatedIssueSelector,
|
||||
@@ -938,7 +950,7 @@ export function createModel (builder: Builder): void {
|
||||
label: recruit.string.Applications
|
||||
},
|
||||
'description',
|
||||
{ key: 'comments', displayProps: { key: 'comments', suffix: true } },
|
||||
'comemnts',
|
||||
{
|
||||
key: '',
|
||||
presenter: tracker.component.RelatedIssueSelector,
|
||||
@@ -1073,6 +1085,10 @@ export function createModel (builder: Builder): void {
|
||||
titleProvider: recruit.function.AppTitleProvider
|
||||
})
|
||||
|
||||
builder.mixin(recruit.class.Applicant, core.class.Class, view.mixin.ObjectIdentifier, {
|
||||
provider: recruit.function.AppIdentifierProvider
|
||||
})
|
||||
|
||||
builder.mixin(recruit.class.Review, core.class.Class, view.mixin.ObjectTitle, {
|
||||
titleProvider: recruit.function.RevTitleProvider
|
||||
})
|
||||
@@ -1569,39 +1585,33 @@ export function createModel (builder: Builder): void {
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
activity.class.ActivityExtension,
|
||||
notification.class.ChatMessageViewlet,
|
||||
core.space.Model,
|
||||
{
|
||||
ofClass: recruit.class.Vacancy,
|
||||
components: {
|
||||
input: chunter.component.CommentInput
|
||||
}
|
||||
objectClass: recruit.class.Vacancy,
|
||||
label: chunter.string.LeftComment
|
||||
},
|
||||
recruit.ids.VacancyActivityExtension
|
||||
recruit.ids.VacancyChatMessageViewlet
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
activity.class.ActivityExtension,
|
||||
notification.class.ChatMessageViewlet,
|
||||
core.space.Model,
|
||||
{
|
||||
ofClass: recruit.class.Applicant,
|
||||
components: {
|
||||
input: chunter.component.CommentInput
|
||||
}
|
||||
objectClass: recruit.class.Applicant,
|
||||
label: chunter.string.LeftComment
|
||||
},
|
||||
recruit.ids.ApplicantActivityExtension
|
||||
recruit.ids.ApplicantChatMessageViewlet
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
activity.class.ActivityExtension,
|
||||
notification.class.ChatMessageViewlet,
|
||||
core.space.Model,
|
||||
{
|
||||
ofClass: recruit.class.Review,
|
||||
components: {
|
||||
input: chunter.component.CommentInput
|
||||
}
|
||||
objectClass: recruit.class.Review,
|
||||
label: chunter.string.LeftComment
|
||||
},
|
||||
recruit.ids.ReviewActivityExtension
|
||||
recruit.ids.ReviewChatMessageViewlet
|
||||
)
|
||||
|
||||
// Allow to use fuzzy search for mixins
|
||||
@@ -1615,7 +1625,7 @@ export function createModel (builder: Builder): void {
|
||||
propagate: [recruit.class.Applicant],
|
||||
propagateClasses: [
|
||||
tags.class.TagReference,
|
||||
chunter.class.Comment,
|
||||
notification.class.ChatMessage,
|
||||
attachment.class.Attachment,
|
||||
contact.class.Channel
|
||||
]
|
||||
@@ -1668,6 +1678,21 @@ export function createModel (builder: Builder): void {
|
||||
}
|
||||
})
|
||||
|
||||
builder.createDoc(
|
||||
notification.class.DocUpdateMessageViewlet,
|
||||
core.space.Model,
|
||||
{
|
||||
objectClass: recruit.class.Applicant,
|
||||
action: 'update',
|
||||
config: {
|
||||
status: {
|
||||
iconPresenter: task.component.StateIconPresenter
|
||||
}
|
||||
}
|
||||
},
|
||||
recruit.ids.NotificationApplicantUpdated
|
||||
)
|
||||
|
||||
createAction(
|
||||
builder,
|
||||
{
|
||||
|
||||
@@ -14,7 +14,12 @@
|
||||
//
|
||||
|
||||
import type { Client, Doc, Ref } from '@hcengineering/core'
|
||||
import { type NotificationGroup, type NotificationType } from '@hcengineering/notification'
|
||||
import {
|
||||
type ChatMessageViewlet,
|
||||
type DocUpdateMessageViewlet,
|
||||
type NotificationGroup,
|
||||
type NotificationType
|
||||
} from '@hcengineering/notification'
|
||||
import type { IntlString, Resource, Status } from '@hcengineering/platform'
|
||||
import { mergeIds } from '@hcengineering/platform'
|
||||
import { recruitId } from '@hcengineering/recruit'
|
||||
@@ -22,7 +27,6 @@ import recruit from '@hcengineering/recruit-resources/src/plugin'
|
||||
import { type ProjectType } from '@hcengineering/task'
|
||||
import type { AnyComponent, Location } from '@hcengineering/ui/src/types'
|
||||
import type { Action, ActionCategory, ViewAction, ViewQueryAction, Viewlet } from '@hcengineering/view'
|
||||
import { type ActivityExtension } from '@hcengineering/activity'
|
||||
|
||||
export default mergeIds(recruitId, recruit, {
|
||||
action: {
|
||||
@@ -84,9 +88,10 @@ export default mergeIds(recruitId, recruit, {
|
||||
AssigneeNotification: '' as Ref<NotificationType>,
|
||||
ApplicationCreateNotification: '' as Ref<NotificationType>,
|
||||
ReviewCreateNotification: '' as Ref<NotificationType>,
|
||||
VacancyActivityExtension: '' as Ref<ActivityExtension>,
|
||||
ApplicantActivityExtension: '' as Ref<ActivityExtension>,
|
||||
ReviewActivityExtension: '' as Ref<ActivityExtension>
|
||||
NotificationApplicantUpdated: '' as Ref<DocUpdateMessageViewlet>,
|
||||
ApplicantChatMessageViewlet: '' as Ref<ChatMessageViewlet>,
|
||||
VacancyChatMessageViewlet: '' as Ref<ChatMessageViewlet>,
|
||||
ReviewChatMessageViewlet: '' as Ref<ChatMessageViewlet>
|
||||
},
|
||||
component: {
|
||||
CreateApplication: '' as AnyComponent,
|
||||
|
||||
@@ -3,12 +3,12 @@ import { type Domain, IndexKind, type Ref } from '@hcengineering/core'
|
||||
import { Collection, Index, Model, Prop, TypeMarkup, TypeRef, TypeString, UX } from '@hcengineering/model'
|
||||
import attachment from '@hcengineering/model-attachment'
|
||||
import calendar, { TEvent } from '@hcengineering/model-calendar'
|
||||
import chunter from '@hcengineering/model-chunter'
|
||||
import contact from '@hcengineering/model-contact'
|
||||
import core, { TAttachedDoc } from '@hcengineering/model-core'
|
||||
import task from '@hcengineering/model-task'
|
||||
import { type Applicant, type Candidate, type Opinion, type Review } from '@hcengineering/recruit'
|
||||
import recruit from './plugin'
|
||||
import notification from '@hcengineering/notification'
|
||||
|
||||
@Model(recruit.class.Review, calendar.class.Event)
|
||||
@UX(recruit.string.Review, recruit.icon.Review, 'RVE', 'number')
|
||||
@@ -47,7 +47,7 @@ export class TOpinion extends TAttachedDoc implements Opinion {
|
||||
@Prop(Collection(attachment.class.Attachment), attachment.string.Attachments, { shortLabel: attachment.string.Files })
|
||||
attachments?: number
|
||||
|
||||
@Prop(Collection(chunter.class.Comment), chunter.string.Comments)
|
||||
@Prop(Collection(notification.class.ChatMessage), notification.string.Comments)
|
||||
comments?: number
|
||||
|
||||
@Prop(TypeMarkup(), recruit.string.Description)
|
||||
|
||||
@@ -25,19 +25,18 @@
|
||||
"prettier-plugin-svelte": "^3.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hcengineering/activity": "^0.6.0",
|
||||
"@hcengineering/contact": "^0.6.20",
|
||||
"@hcengineering/core": "^0.6.28",
|
||||
"@hcengineering/model": "^0.6.7",
|
||||
"@hcengineering/ui": "^0.6.11",
|
||||
"@hcengineering/contact": "^0.6.20",
|
||||
"@hcengineering/platform": "^0.6.9",
|
||||
"@hcengineering/model-core": "^0.6.0",
|
||||
"@hcengineering/model-view": "^0.6.0",
|
||||
"@hcengineering/model-chunter": "^0.6.0",
|
||||
"@hcengineering/request": "^0.6.6",
|
||||
"@hcengineering/notification": "^0.6.16",
|
||||
"@hcengineering/model-core": "^0.6.0",
|
||||
"@hcengineering/model-notification": "^0.6.0",
|
||||
"@hcengineering/activity": "^0.6.0",
|
||||
"@hcengineering/chunter": "^0.6.12",
|
||||
"@hcengineering/request-resources": "^0.6.0"
|
||||
"@hcengineering/model-view": "^0.6.0",
|
||||
"@hcengineering/notification": "^0.6.16",
|
||||
"@hcengineering/platform": "^0.6.9",
|
||||
"@hcengineering/request": "^0.6.6",
|
||||
"@hcengineering/request-resources": "^0.6.0",
|
||||
"@hcengineering/ui": "^0.6.11"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
//
|
||||
|
||||
import activity from '@hcengineering/activity'
|
||||
import chunter from '@hcengineering/chunter'
|
||||
import type { PersonAccount } from '@hcengineering/contact'
|
||||
import contact from '@hcengineering/contact'
|
||||
import { type Domain, IndexKind, type Ref, type Tx } from '@hcengineering/core'
|
||||
@@ -73,11 +72,11 @@ export class TRequest extends TAttachedDoc implements Request {
|
||||
@ReadOnly()
|
||||
rejected?: Ref<PersonAccount>
|
||||
|
||||
@Prop(Collection(chunter.class.Comment), chunter.string.Comments)
|
||||
@Prop(Collection(notification.class.ChatMessage), notification.string.Comments)
|
||||
comments?: number
|
||||
}
|
||||
|
||||
@Mixin(request.mixin.RequestDecisionComment, chunter.class.Comment)
|
||||
@Mixin(request.mixin.RequestDecisionComment, notification.class.ChatMessage)
|
||||
export class TRequestDecisionComment extends TComment implements RequestDecisionComment {}
|
||||
|
||||
@Mixin(request.mixin.RequestPresenter, core.class.Class)
|
||||
|
||||
@@ -33,7 +33,7 @@ export function createModel (builder: Builder): void {
|
||||
})
|
||||
|
||||
builder.mixin<Class<Doc>, ObjectDDParticipant>(
|
||||
chunter.class.Comment,
|
||||
notification.class.ChatMessage,
|
||||
core.class.Class,
|
||||
serverCore.mixin.ObjectDDParticipant,
|
||||
{
|
||||
|
||||
@@ -15,12 +15,12 @@
|
||||
"devDependencies": {
|
||||
"@hcengineering/platform-rig": "^0.6.0",
|
||||
"@typescript-eslint/eslint-plugin": "^6.11.0",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-promise": "^6.1.1",
|
||||
"eslint-plugin-n": "^15.4.0",
|
||||
"eslint": "^8.54.0",
|
||||
"@typescript-eslint/parser": "^6.11.0",
|
||||
"eslint": "^8.54.0",
|
||||
"eslint-config-standard-with-typescript": "^40.0.0",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-n": "^15.4.0",
|
||||
"eslint-plugin-promise": "^6.1.1",
|
||||
"prettier": "^3.1.0",
|
||||
"prettier-plugin-svelte": "^3.1.0"
|
||||
},
|
||||
@@ -28,10 +28,11 @@
|
||||
"@hcengineering/core": "^0.6.28",
|
||||
"@hcengineering/model": "^0.6.7",
|
||||
"@hcengineering/model-core": "^0.6.0",
|
||||
"@hcengineering/platform": "^0.6.9",
|
||||
"@hcengineering/notification": "^0.6.16",
|
||||
"@hcengineering/model-notification": "^0.6.0",
|
||||
"@hcengineering/notification": "^0.6.16",
|
||||
"@hcengineering/platform": "^0.6.9",
|
||||
"@hcengineering/server-core": "^0.6.1",
|
||||
"@hcengineering/server-notification": "^0.6.1",
|
||||
"@hcengineering/server-core": "^0.6.1"
|
||||
"@hcengineering/server-notification-resources": "^0.6.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ import serverNotification, {
|
||||
} from '@hcengineering/server-notification'
|
||||
|
||||
export { serverNotificationId } from '@hcengineering/server-notification'
|
||||
export { notificationServerOperation } from './migration'
|
||||
|
||||
@Mixin(serverNotification.mixin.HTMLPresenter, core.class.Class)
|
||||
export class THTMLPresenter extends TClass implements HTMLPresenter {
|
||||
@@ -62,8 +63,25 @@ export function createModel (builder: Builder): void {
|
||||
trigger: serverNotification.trigger.OnBacklinkCreate
|
||||
})
|
||||
|
||||
// NOTE: temporarily disabled
|
||||
// builder.createDoc(serverCore.class.Trigger, core.space.Model, {
|
||||
// trigger: serverNotification.trigger.OnReactionChanged,
|
||||
// txMatch: {
|
||||
// collection: 'reactions',
|
||||
// objectClass: notification.class.ActivityMessage,
|
||||
// _class: core.class.TxCollectionCUD
|
||||
// }
|
||||
// })
|
||||
|
||||
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
|
||||
trigger: serverNotification.trigger.CollaboratorDocHandler
|
||||
trigger: serverNotification.trigger.NotificationMessagesHandler
|
||||
})
|
||||
|
||||
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
|
||||
trigger: serverNotification.trigger.OnChatMessageSent,
|
||||
txMatch: {
|
||||
objectClass: notification.class.ChatMessage
|
||||
}
|
||||
})
|
||||
|
||||
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
|
||||
|
||||
@@ -0,0 +1,210 @@
|
||||
//
|
||||
// Copyright © 2023 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 core, {
|
||||
SortingOrder,
|
||||
TxFactory,
|
||||
toFindResult,
|
||||
toIdMap,
|
||||
type AttachedDoc,
|
||||
type Class,
|
||||
type Doc,
|
||||
type Ref,
|
||||
type TxCUD,
|
||||
type TxCollectionCUD,
|
||||
type TxCreateDoc,
|
||||
TxProcessor
|
||||
} from '@hcengineering/core'
|
||||
import { type MigrateOperation, type MigrationClient, type MigrationIterator, tryMigrate } from '@hcengineering/model'
|
||||
import notification, { type DocUpdateMessage } from '@hcengineering/notification'
|
||||
import { getAllObjectTransactions, type DocObjectCache, serverNotificationId } from '@hcengineering/server-notification'
|
||||
import { generateDocUpdateMessages, type NotificationControl } from '@hcengineering/server-notification-resources'
|
||||
|
||||
function getNotificationControl (client: MigrationClient): NotificationControl {
|
||||
const txFactory = new TxFactory(core.account.System, false)
|
||||
|
||||
return {
|
||||
txFactory,
|
||||
modelDb: client.model,
|
||||
hierarchy: client.hierarchy,
|
||||
findAll: async (_class, query, options) =>
|
||||
toFindResult(await client.find(client.hierarchy.getDomain(_class), query, options))
|
||||
}
|
||||
}
|
||||
|
||||
async function generateDocUpdateMessageByTx (
|
||||
tx: TxCUD<Doc>,
|
||||
control: NotificationControl,
|
||||
client: MigrationClient,
|
||||
objectCache?: DocObjectCache
|
||||
): Promise<void> {
|
||||
const createCollectionCUDTxes = await generateDocUpdateMessages(tx, control, undefined, undefined, objectCache)
|
||||
|
||||
for (const collectionTx of createCollectionCUDTxes) {
|
||||
const createTx = collectionTx.tx as TxCreateDoc<DocUpdateMessage>
|
||||
const domain = client.hierarchy.getDomain(createTx.objectClass)
|
||||
|
||||
await client.create<DocUpdateMessage>(domain, {
|
||||
_id: createTx.objectId,
|
||||
_class: createTx.objectClass,
|
||||
space: createTx.objectSpace,
|
||||
modifiedOn: createTx.modifiedOn,
|
||||
modifiedBy: createTx.modifiedBy,
|
||||
createdOn: createTx.modifiedOn,
|
||||
createdBy: createTx.createdBy,
|
||||
...createTx.attributes
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async function createDocUpdateMessages (client: MigrationClient): Promise<void> {
|
||||
const activityDocs = await client.model.findAll(notification.mixin.ActivityDoc, {
|
||||
_id: { $nin: [notification.class.DocUpdateMessage, notification.class.ChatMessage] }
|
||||
})
|
||||
const activityDocClasses = activityDocs.map(({ _id }) => _id)
|
||||
|
||||
const notificationControl = getNotificationControl(client)
|
||||
|
||||
const txClient: Pick<NotificationControl, 'hierarchy' | 'findAll'> = {
|
||||
hierarchy: notificationControl.hierarchy,
|
||||
findAll: notificationControl.findAll
|
||||
}
|
||||
|
||||
let processed = 0
|
||||
|
||||
async function generateFor (_class: Ref<Class<Doc>>, documents: MigrationIterator<Doc>): Promise<void> {
|
||||
while (true) {
|
||||
const docs = await documents.next(50)
|
||||
|
||||
if (docs == null || docs.length === 0) {
|
||||
break
|
||||
}
|
||||
const allTransactions = await getAllObjectTransactions(
|
||||
txClient,
|
||||
_class,
|
||||
docs.map((it) => it._id)
|
||||
)
|
||||
|
||||
// We need to find parent collection objects if missing
|
||||
const byClass = new Map<Ref<Class<Doc>>, Set<Ref<Doc>>>()
|
||||
for (const vv of allTransactions.values()) {
|
||||
for (const v of vv) {
|
||||
try {
|
||||
const _cl = client.hierarchy.getBaseClass(v.objectClass)
|
||||
const s = byClass.get(_cl) ?? new Set()
|
||||
s.add(v.objectId)
|
||||
byClass.set(_cl, s)
|
||||
} catch {
|
||||
console.log('class not found:', v.objectClass)
|
||||
continue
|
||||
}
|
||||
|
||||
if (v._class === core.class.TxCollectionCUD) {
|
||||
try {
|
||||
const vcol = v as TxCollectionCUD<Doc, AttachedDoc>
|
||||
const _cl = client.hierarchy.getBaseClass(vcol.tx.objectClass)
|
||||
const s = byClass.get(_cl) ?? new Set()
|
||||
s.add(vcol.tx.objectId)
|
||||
byClass.set(_cl, s)
|
||||
} catch {
|
||||
console.log('class not found:', (v as TxCollectionCUD<Doc, AttachedDoc>).tx.objectClass)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const docIds: Map<Ref<Doc>, Doc | null> = toIdMap(docs)
|
||||
|
||||
for (const [_class, classDocs] of byClass.entries()) {
|
||||
const ids: Ref<Doc>[] = Array.from(classDocs.values()).filter((it) => !docIds.has(it))
|
||||
if (ids.length > 0) {
|
||||
for (const di of ids) {
|
||||
docIds.set(di, null)
|
||||
}
|
||||
const edocs = await txClient.findAll(_class, { _id: { $in: ids } })
|
||||
for (const ed of edocs) {
|
||||
docIds.set(ed._id, ed)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const d of docs) {
|
||||
processed += 1
|
||||
if (processed % 1000 === 0) {
|
||||
console.log('processed', processed)
|
||||
}
|
||||
const transactions = allTransactions.get(d._id) ?? []
|
||||
for (const tx of transactions) {
|
||||
const innerTx = TxProcessor.extractTx(tx) as TxCUD<Doc>
|
||||
|
||||
if (!client.hierarchy.hasClass(innerTx.objectClass)) {
|
||||
console.log('class not found:', innerTx.objectClass)
|
||||
continue
|
||||
}
|
||||
|
||||
try {
|
||||
await generateDocUpdateMessageByTx(tx, notificationControl, client, {
|
||||
docs: docIds,
|
||||
transactions: allTransactions
|
||||
})
|
||||
} catch (e: any) {
|
||||
console.error('error processing:', d._id, e.stack)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
await documents.close()
|
||||
}
|
||||
|
||||
for (const activityClass of activityDocClasses) {
|
||||
console.log('processing', activityClass)
|
||||
|
||||
if (client.hierarchy.isMixin(activityClass)) {
|
||||
// Skip mixins
|
||||
continue
|
||||
}
|
||||
|
||||
const domain = client.hierarchy.getDomain(activityClass)
|
||||
|
||||
await generateFor(
|
||||
activityClass,
|
||||
await client.traverse<Doc>(
|
||||
domain,
|
||||
{
|
||||
_class: activityClass,
|
||||
space: { $ne: core.space.Model }
|
||||
},
|
||||
{
|
||||
sort: {
|
||||
createdOn: SortingOrder.Ascending
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
console.log('process-finished', processed)
|
||||
}
|
||||
|
||||
export const notificationServerOperation: MigrateOperation = {
|
||||
async migrate (client: MigrationClient): Promise<void> {
|
||||
await tryMigrate(client, serverNotificationId, [
|
||||
{
|
||||
state: 'notifications',
|
||||
func: createDocUpdateMessages
|
||||
}
|
||||
])
|
||||
},
|
||||
async upgrade (): Promise<void> {}
|
||||
}
|
||||
@@ -27,11 +27,11 @@
|
||||
"dependencies": {
|
||||
"@hcengineering/core": "^0.6.28",
|
||||
"@hcengineering/model": "^0.6.7",
|
||||
"@hcengineering/platform": "^0.6.9",
|
||||
"@hcengineering/model-core": "^0.6.0",
|
||||
"@hcengineering/model-recruit": "^0.6.0",
|
||||
"@hcengineering/notification": "^0.6.16",
|
||||
"@hcengineering/openai": "^0.6.0",
|
||||
"@hcengineering/server-core": "^0.6.1",
|
||||
"@hcengineering/model-chunter": "^0.6.0",
|
||||
"@hcengineering/model-recruit": "^0.6.0"
|
||||
"@hcengineering/platform": "^0.6.9",
|
||||
"@hcengineering/server-core": "^0.6.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,8 +22,8 @@ import core, { DOMAIN_CONFIGURATION } from '@hcengineering/core'
|
||||
import openai, { type OpenAIConfiguration } from '@hcengineering/openai/src/plugin'
|
||||
import serverCore from '@hcengineering/server-core'
|
||||
|
||||
import chunter from '@hcengineering/model-chunter'
|
||||
import recruit from '@hcengineering/model-recruit'
|
||||
import notification from '@hcengineering/notification'
|
||||
|
||||
export { openAIId } from '@hcengineering/openai/src/plugin'
|
||||
|
||||
@@ -49,7 +49,7 @@ export function createModel (builder: Builder): void {
|
||||
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
|
||||
trigger: openai.trigger.AsyncOnGPTRequest,
|
||||
txMatch: {
|
||||
objectClass: { $in: [chunter.class.Comment, recruit.class.ApplicantMatch] },
|
||||
objectClass: { $in: [notification.class.ChatMessage, recruit.class.ApplicantMatch] },
|
||||
_class: core.class.TxCreateDoc
|
||||
}
|
||||
})
|
||||
|
||||
@@ -328,6 +328,19 @@ export function createModel (builder: Builder): void {
|
||||
setting.ids.TxIntegrationDisable
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
notification.class.DocUpdateMessageViewlet,
|
||||
core.space.Model,
|
||||
{
|
||||
objectClass: setting.class.Integration,
|
||||
icon: setting.icon.Integrations,
|
||||
label: setting.string.IntegrationWith,
|
||||
action: 'update',
|
||||
hideIfRemoved: true
|
||||
},
|
||||
setting.ids.UpdateIntegrationNotificationViewlet
|
||||
)
|
||||
|
||||
builder.mixin(core.class.TypeString, core.class.Class, view.mixin.ObjectEditor, {
|
||||
editor: setting.component.StringTypeEditor
|
||||
})
|
||||
|
||||
@@ -21,7 +21,11 @@ import setting from '@hcengineering/setting-resources/src/plugin'
|
||||
import { type AnyComponent } from '@hcengineering/ui/src/types'
|
||||
import { type Action, type ActionCategory, type ViewAction } from '@hcengineering/view'
|
||||
import { type TemplateFieldFunc } from '@hcengineering/templates'
|
||||
import { type NotificationGroup, type NotificationType } from '@hcengineering/notification'
|
||||
import {
|
||||
type DocUpdateMessageViewlet,
|
||||
type NotificationGroup,
|
||||
type NotificationType
|
||||
} from '@hcengineering/notification'
|
||||
|
||||
export default mergeIds(settingId, setting, {
|
||||
activity: {
|
||||
@@ -32,7 +36,8 @@ export default mergeIds(settingId, setting, {
|
||||
EnumSetting: '' as Ref<Doc>,
|
||||
Configure: '' as Ref<Doc>,
|
||||
SettingNotificationGroup: '' as Ref<NotificationGroup>,
|
||||
IntegrationDisabledNotification: '' as Ref<NotificationType>
|
||||
IntegrationDisabledNotification: '' as Ref<NotificationType>,
|
||||
UpdateIntegrationNotificationViewlet: '' as Ref<DocUpdateMessageViewlet>
|
||||
},
|
||||
component: {
|
||||
EnumSetting: '' as AnyComponent,
|
||||
|
||||
@@ -15,25 +15,25 @@
|
||||
"devDependencies": {
|
||||
"@hcengineering/platform-rig": "^0.6.0",
|
||||
"@typescript-eslint/eslint-plugin": "^6.11.0",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-promise": "^6.1.1",
|
||||
"eslint-plugin-n": "^15.4.0",
|
||||
"eslint": "^8.54.0",
|
||||
"@typescript-eslint/parser": "^6.11.0",
|
||||
"eslint": "^8.54.0",
|
||||
"eslint-config-standard-with-typescript": "^40.0.0",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-n": "^15.4.0",
|
||||
"eslint-plugin-promise": "^6.1.1",
|
||||
"prettier": "^3.1.0",
|
||||
"prettier-plugin-svelte": "^3.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hcengineering/activity": "^0.6.0",
|
||||
"@hcengineering/model": "^0.6.7",
|
||||
"@hcengineering/core": "^0.6.28",
|
||||
"@hcengineering/model": "^0.6.7",
|
||||
"@hcengineering/model-core": "^0.6.0",
|
||||
"@hcengineering/model-view": "^0.6.0",
|
||||
"@hcengineering/notification": "^0.6.16",
|
||||
"@hcengineering/platform": "^0.6.9",
|
||||
"@hcengineering/tags": "^0.6.12",
|
||||
"@hcengineering/tags-resources": "^0.6.0",
|
||||
"@hcengineering/ui": "^0.6.11",
|
||||
"@hcengineering/model-core": "^0.6.0",
|
||||
"@hcengineering/view": "^0.6.9",
|
||||
"@hcengineering/model-view": "^0.6.0"
|
||||
"@hcengineering/view": "^0.6.9"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,24 +25,23 @@
|
||||
"prettier-plugin-svelte": "^3.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hcengineering/contact": "^0.6.20",
|
||||
"@hcengineering/core": "^0.6.28",
|
||||
"@hcengineering/model": "^0.6.7",
|
||||
"@hcengineering/ui": "^0.6.11",
|
||||
"@hcengineering/contact": "^0.6.20",
|
||||
"@hcengineering/platform": "^0.6.9",
|
||||
"@hcengineering/model-attachment": "^0.6.0",
|
||||
"@hcengineering/model-contact": "^0.6.1",
|
||||
"@hcengineering/model-core": "^0.6.0",
|
||||
"@hcengineering/model-presentation": "^0.6.0",
|
||||
"@hcengineering/model-tags": "^0.6.0",
|
||||
"@hcengineering/model-view": "^0.6.0",
|
||||
"@hcengineering/model-workbench": "^0.6.1",
|
||||
"@hcengineering/model-contact": "^0.6.1",
|
||||
"@hcengineering/model-attachment": "^0.6.0",
|
||||
"@hcengineering/notification": "^0.6.16",
|
||||
"@hcengineering/platform": "^0.6.9",
|
||||
"@hcengineering/tags": "^0.6.12",
|
||||
"@hcengineering/task": "^0.6.13",
|
||||
"@hcengineering/task-resources": "^0.6.0",
|
||||
"@hcengineering/model-chunter": "^0.6.0",
|
||||
"@hcengineering/workbench": "^0.6.9",
|
||||
"@hcengineering/ui": "^0.6.11",
|
||||
"@hcengineering/view": "^0.6.9",
|
||||
"@hcengineering/model-presentation": "^0.6.0",
|
||||
"@hcengineering/tags": "^0.6.12",
|
||||
"@hcengineering/model-tags": "^0.6.0"
|
||||
"@hcengineering/workbench": "^0.6.9"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,7 +42,6 @@ import {
|
||||
UX
|
||||
} from '@hcengineering/model'
|
||||
import attachment from '@hcengineering/model-attachment'
|
||||
import chunter from '@hcengineering/model-chunter'
|
||||
import core, { TAttachedDoc, TClass, TDoc, TSpace } from '@hcengineering/model-core'
|
||||
import view, { createAction, template, actionTemplates as viewTemplates } from '@hcengineering/model-view'
|
||||
import { type IntlString } from '@hcengineering/platform'
|
||||
@@ -61,6 +60,7 @@ import {
|
||||
import type { AnyComponent } from '@hcengineering/ui/src/types'
|
||||
import { type ViewAction } from '@hcengineering/view'
|
||||
import task from './plugin'
|
||||
import notification from '@hcengineering/notification'
|
||||
|
||||
export { taskId } from '@hcengineering/task'
|
||||
export { createProjectType, createSequence, taskOperation } from './migration'
|
||||
@@ -95,7 +95,7 @@ export class TTask extends TAttachedDoc implements Task {
|
||||
@Prop(Collection(tags.class.TagReference, task.string.TaskLabels), task.string.TaskLabels)
|
||||
labels?: number
|
||||
|
||||
@Prop(Collection(chunter.class.Comment), chunter.string.Comments)
|
||||
@Prop(Collection(notification.class.ChatMessage), notification.string.Comments)
|
||||
comments?: number
|
||||
|
||||
@Prop(Collection(attachment.class.Attachment), attachment.string.Attachments, { shortLabel: attachment.string.Files })
|
||||
|
||||
@@ -59,7 +59,8 @@ export default mergeIds(taskId, task, {
|
||||
StateRefPresenter: '' as AnyComponent,
|
||||
StatusSelector: '' as AnyComponent,
|
||||
TemplatesIcon: '' as AnyComponent,
|
||||
TypesView: '' as AnyComponent
|
||||
TypesView: '' as AnyComponent,
|
||||
StateIconPresenter: '' as AnyComponent
|
||||
},
|
||||
space: {
|
||||
TasksPublic: '' as Ref<Space>
|
||||
|
||||
@@ -133,6 +133,19 @@ export function createModel (builder: Builder): void {
|
||||
telegram.ids.TxMessage
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
notification.class.DocUpdateMessageViewlet,
|
||||
core.space.Model,
|
||||
{
|
||||
objectClass: telegram.class.Message,
|
||||
action: 'create',
|
||||
icon: contact.icon.Telegram,
|
||||
component: telegram.notification.NotificationMessageCreated,
|
||||
label: telegram.string.SharedMessages
|
||||
},
|
||||
telegram.ids.NotificationMessageCreated
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
contact.class.ChannelProvider,
|
||||
core.space.Model,
|
||||
@@ -177,6 +190,20 @@ export function createModel (builder: Builder): void {
|
||||
telegram.ids.TxSharedCreate
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
notification.class.DocUpdateMessageViewlet,
|
||||
core.space.Model,
|
||||
{
|
||||
objectClass: telegram.class.SharedMessages,
|
||||
action: 'create',
|
||||
icon: contact.icon.Telegram,
|
||||
component: telegram.component.SharedMessages,
|
||||
label: telegram.string.SharedMessages,
|
||||
hideIfRemoved: true
|
||||
},
|
||||
telegram.ids.NotificationMessageShared
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
notification.class.NotificationGroup,
|
||||
core.space.Model,
|
||||
|
||||
@@ -21,7 +21,7 @@ import telegram from '@hcengineering/telegram-resources/src/plugin'
|
||||
import type { AnyComponent } from '@hcengineering/ui/src/types'
|
||||
import type { TxViewlet } from '@hcengineering/activity'
|
||||
import { type TemplateFieldFunc } from '@hcengineering/templates'
|
||||
import { type NotificationGroup } from '@hcengineering/notification'
|
||||
import { type DocUpdateMessageViewlet, type NotificationGroup } from '@hcengineering/notification'
|
||||
|
||||
export default mergeIds(telegramId, telegram, {
|
||||
string: {
|
||||
@@ -42,7 +42,9 @@ export default mergeIds(telegramId, telegram, {
|
||||
TxMessage: '' as Ref<TxViewlet>,
|
||||
TxSharedCreate: '' as Ref<TxViewlet>,
|
||||
NewMessageNotificationViewlet: '' as Ref<TxViewlet>,
|
||||
NotificationGroup: '' as Ref<NotificationGroup>
|
||||
NotificationGroup: '' as Ref<NotificationGroup>,
|
||||
NotificationMessageShared: '' as Ref<DocUpdateMessageViewlet>,
|
||||
NotificationMessageCreated: '' as Ref<DocUpdateMessageViewlet>
|
||||
},
|
||||
function: {
|
||||
GetCurrentEmployeeTG: '' as Resource<TemplateFieldFunc>,
|
||||
|
||||
+12
-13
@@ -26,27 +26,26 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@hcengineering/activity": "^0.6.0",
|
||||
"@hcengineering/contact": "^0.6.20",
|
||||
"@hcengineering/core": "^0.6.28",
|
||||
"@hcengineering/model": "^0.6.7",
|
||||
"@hcengineering/ui": "^0.6.11",
|
||||
"@hcengineering/contact": "^0.6.20",
|
||||
"@hcengineering/platform": "^0.6.9",
|
||||
"@hcengineering/model-attachment": "^0.6.0",
|
||||
"@hcengineering/model-contact": "^0.6.1",
|
||||
"@hcengineering/model-core": "^0.6.0",
|
||||
"@hcengineering/model-notification": "^0.6.0",
|
||||
"@hcengineering/model-presentation": "^0.6.0",
|
||||
"@hcengineering/model-task": "^0.6.0",
|
||||
"@hcengineering/model-view": "^0.6.0",
|
||||
"@hcengineering/model-workbench": "^0.6.1",
|
||||
"@hcengineering/model-contact": "^0.6.1",
|
||||
"@hcengineering/model-attachment": "^0.6.0",
|
||||
"@hcengineering/notification": "^0.6.16",
|
||||
"@hcengineering/model-notification": "^0.6.0",
|
||||
"@hcengineering/platform": "^0.6.9",
|
||||
"@hcengineering/setting": "^0.6.11",
|
||||
"@hcengineering/tags": "^0.6.12",
|
||||
"@hcengineering/task": "^0.6.13",
|
||||
"@hcengineering/tracker": "^0.6.13",
|
||||
"@hcengineering/tracker-resources": "^0.6.0",
|
||||
"@hcengineering/model-task": "^0.6.0",
|
||||
"@hcengineering/model-chunter": "^0.6.0",
|
||||
"@hcengineering/workbench": "^0.6.9",
|
||||
"@hcengineering/ui": "^0.6.11",
|
||||
"@hcengineering/view": "^0.6.9",
|
||||
"@hcengineering/model-presentation": "^0.6.0",
|
||||
"@hcengineering/setting": "^0.6.11",
|
||||
"@hcengineering/task": "^0.6.13",
|
||||
"@hcengineering/tags": "^0.6.12"
|
||||
"@hcengineering/workbench": "^0.6.9"
|
||||
}
|
||||
}
|
||||
|
||||
+114
-25
@@ -18,7 +18,6 @@ import { type Builder } from '@hcengineering/model'
|
||||
import core from '@hcengineering/model-core'
|
||||
import task from '@hcengineering/model-task'
|
||||
import view from '@hcengineering/model-view'
|
||||
import chunter from '@hcengineering/model-chunter'
|
||||
import workbench from '@hcengineering/model-workbench'
|
||||
import notification from '@hcengineering/notification'
|
||||
import setting from '@hcengineering/setting'
|
||||
@@ -259,6 +258,10 @@ function defineFilters (builder: Builder): void {
|
||||
component: tracker.component.MilestoneFilter
|
||||
})
|
||||
|
||||
builder.mixin(tracker.class.Milestone, core.class.Class, view.mixin.ObjectTitle, {
|
||||
titleProvider: tracker.function.MilestoneTitleProvider
|
||||
})
|
||||
|
||||
//
|
||||
// Project
|
||||
//
|
||||
@@ -280,6 +283,10 @@ function defineFilters (builder: Builder): void {
|
||||
filters: []
|
||||
})
|
||||
|
||||
builder.mixin(tracker.class.Component, core.class.Class, view.mixin.ObjectTitle, {
|
||||
titleProvider: tracker.function.ComponentTitleProvider
|
||||
})
|
||||
|
||||
//
|
||||
// Type Milestone Status
|
||||
//
|
||||
@@ -438,6 +445,20 @@ export function createModel (builder: Builder): void {
|
||||
TTypeRemainingTime
|
||||
)
|
||||
|
||||
builder.mixin(tracker.class.Project, core.class.Class, notification.mixin.ActivityDoc, {})
|
||||
builder.mixin(tracker.class.Issue, core.class.Class, notification.mixin.ActivityDoc, {
|
||||
ignoreCollections: ['comments']
|
||||
})
|
||||
builder.mixin(tracker.class.Milestone, core.class.Class, notification.mixin.ActivityDoc, {
|
||||
ignoreCollections: ['comments']
|
||||
})
|
||||
builder.mixin(tracker.class.Component, core.class.Class, notification.mixin.ActivityDoc, {
|
||||
ignoreCollections: ['comments']
|
||||
})
|
||||
builder.mixin(tracker.class.IssueTemplate, core.class.Class, notification.mixin.ActivityDoc, {
|
||||
ignoreCollections: ['comments']
|
||||
})
|
||||
|
||||
defineViewlets(builder)
|
||||
|
||||
defineStatusCategories(builder)
|
||||
@@ -495,6 +516,82 @@ export function createModel (builder: Builder): void {
|
||||
tracker.ids.TxIssueCreated
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
notification.class.DocUpdateMessageViewlet,
|
||||
core.space.Model,
|
||||
{
|
||||
objectClass: tracker.class.Issue,
|
||||
action: 'update',
|
||||
icon: tracker.icon.Issue,
|
||||
config: {
|
||||
status: {
|
||||
iconPresenter: tracker.component.IssueStatusIcon
|
||||
},
|
||||
priority: {
|
||||
iconPresenter: tracker.component.PriorityIconPresenter
|
||||
},
|
||||
estimation: {
|
||||
icon: tracker.icon.Estimation
|
||||
}
|
||||
}
|
||||
},
|
||||
tracker.ids.NotificationIssueUpdated
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
notification.class.DocUpdateMessageViewlet,
|
||||
core.space.Model,
|
||||
{
|
||||
objectClass: tracker.class.Issue,
|
||||
action: 'create',
|
||||
icon: tracker.icon.Issue,
|
||||
valueAttr: 'title'
|
||||
},
|
||||
tracker.ids.NotificationIssueCreated
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
notification.class.DocUpdateMessageViewlet,
|
||||
core.space.Model,
|
||||
{
|
||||
objectClass: tracker.class.Issue,
|
||||
action: 'remove',
|
||||
icon: tracker.icon.Issue,
|
||||
valueAttr: 'title'
|
||||
},
|
||||
tracker.ids.NotificationIssueRemoved
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
notification.class.DocUpdateMessageViewlet,
|
||||
core.space.Model,
|
||||
{
|
||||
objectClass: tracker.class.Milestone,
|
||||
action: 'update',
|
||||
config: {
|
||||
status: {
|
||||
iconPresenter: tracker.component.MilestoneStatusIcon
|
||||
}
|
||||
}
|
||||
},
|
||||
tracker.ids.NotificationMilestoneUpdated
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
notification.class.DocUpdateMessageViewlet,
|
||||
core.space.Model,
|
||||
{
|
||||
objectClass: tracker.class.IssueTemplate,
|
||||
action: 'update',
|
||||
config: {
|
||||
priority: {
|
||||
iconPresenter: tracker.component.PriorityIconPresenter
|
||||
}
|
||||
}
|
||||
},
|
||||
tracker.ids.NotificationIssueTemplateUpdated
|
||||
)
|
||||
|
||||
defineApplication(builder, { myIssuesId, allIssuesId, issuesId, componentsId, milestonesId, templatesId })
|
||||
|
||||
defineActions(builder, issuesId, componentsId, myIssuesId)
|
||||
@@ -528,51 +625,43 @@ export function createModel (builder: Builder): void {
|
||||
})
|
||||
|
||||
builder.createDoc(
|
||||
activity.class.ActivityExtension,
|
||||
notification.class.ChatMessageViewlet,
|
||||
core.space.Model,
|
||||
{
|
||||
ofClass: tracker.class.Issue,
|
||||
components: {
|
||||
input: chunter.component.CommentInput
|
||||
}
|
||||
objectClass: tracker.class.Issue,
|
||||
label: notification.string.LeftComment
|
||||
},
|
||||
tracker.ids.IssueActivityExtension
|
||||
tracker.ids.IssueChatMessageViewlet
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
activity.class.ActivityExtension,
|
||||
notification.class.ChatMessageViewlet,
|
||||
core.space.Model,
|
||||
{
|
||||
ofClass: tracker.class.IssueTemplate,
|
||||
components: {
|
||||
input: chunter.component.CommentInput
|
||||
}
|
||||
objectClass: tracker.class.IssueTemplate,
|
||||
label: notification.string.LeftComment
|
||||
},
|
||||
tracker.ids.IssueTemplateActivityExtension
|
||||
tracker.ids.IssueTemplateChatMessageViewlet
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
activity.class.ActivityExtension,
|
||||
notification.class.ChatMessageViewlet,
|
||||
core.space.Model,
|
||||
{
|
||||
ofClass: tracker.class.Component,
|
||||
components: {
|
||||
input: chunter.component.CommentInput
|
||||
}
|
||||
objectClass: tracker.class.Component,
|
||||
label: notification.string.LeftComment
|
||||
},
|
||||
tracker.ids.ComponentActivityExtension
|
||||
tracker.ids.ComponentChatMessageViewlet
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
activity.class.ActivityExtension,
|
||||
notification.class.ChatMessageViewlet,
|
||||
core.space.Model,
|
||||
{
|
||||
ofClass: tracker.class.Milestone,
|
||||
components: {
|
||||
input: chunter.component.CommentInput
|
||||
}
|
||||
objectClass: tracker.class.Milestone,
|
||||
label: notification.string.LeftComment
|
||||
},
|
||||
tracker.ids.MilestoneActivityExtension
|
||||
tracker.ids.MilestoneChatMessageViewlet
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
|
||||
@@ -22,8 +22,13 @@ 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 Application } from '@hcengineering/workbench'
|
||||
import { type ActivityExtension, type TxViewlet } from '@hcengineering/activity'
|
||||
import { type NotificationGroup, type NotificationType } from '@hcengineering/notification'
|
||||
import { type TxViewlet } from '@hcengineering/activity'
|
||||
import {
|
||||
type ChatMessageViewlet,
|
||||
type DocUpdateMessageViewlet,
|
||||
type NotificationGroup,
|
||||
type NotificationType
|
||||
} from '@hcengineering/notification'
|
||||
|
||||
export default mergeIds(trackerId, tracker, {
|
||||
string: {
|
||||
@@ -75,11 +80,16 @@ export default mergeIds(trackerId, tracker, {
|
||||
TxIssueCreated: '' as Ref<TxViewlet>,
|
||||
TrackerNotificationGroup: '' as Ref<NotificationGroup>,
|
||||
AssigneeNotification: '' as Ref<NotificationType>,
|
||||
IssueActivityExtension: '' as Ref<ActivityExtension>,
|
||||
IssueTemplateActivityExtension: '' as Ref<ActivityExtension>,
|
||||
ComponentActivityExtension: '' as Ref<ActivityExtension>,
|
||||
MilestoneActivityExtension: '' as Ref<ActivityExtension>,
|
||||
BaseProjectType: '' as Ref<ProjectType>
|
||||
BaseProjectType: '' as Ref<ProjectType>,
|
||||
NotificationIssueUpdated: '' as Ref<DocUpdateMessageViewlet>,
|
||||
NotificationIssueCreated: '' as Ref<DocUpdateMessageViewlet>,
|
||||
NotificationIssueRemoved: '' as Ref<DocUpdateMessageViewlet>,
|
||||
NotificationMilestoneUpdated: '' as Ref<DocUpdateMessageViewlet>,
|
||||
NotificationIssueTemplateUpdated: '' as Ref<DocUpdateMessageViewlet>,
|
||||
IssueChatMessageViewlet: '' as Ref<ChatMessageViewlet>,
|
||||
IssueTemplateChatMessageViewlet: '' as Ref<ChatMessageViewlet>,
|
||||
ComponentChatMessageViewlet: '' as Ref<ChatMessageViewlet>,
|
||||
MilestoneChatMessageViewlet: '' as Ref<ChatMessageViewlet>
|
||||
},
|
||||
completion: {
|
||||
IssueQuery: '' as Resource<ObjectSearchFactory>,
|
||||
|
||||
@@ -41,7 +41,6 @@ import {
|
||||
UX
|
||||
} from '@hcengineering/model'
|
||||
import attachment from '@hcengineering/model-attachment'
|
||||
import chunter from '@hcengineering/model-chunter'
|
||||
import core, { TAttachedDoc, TDoc, TStatus, TType } from '@hcengineering/model-core'
|
||||
import task, { TTask, TProject as TTaskProject } from '@hcengineering/model-task'
|
||||
import { type IntlString } from '@hcengineering/platform'
|
||||
@@ -65,6 +64,7 @@ import {
|
||||
type TimeSpendReport
|
||||
} from '@hcengineering/tracker'
|
||||
import tracker from './plugin'
|
||||
import notification from '@hcengineering/notification'
|
||||
|
||||
export const DOMAIN_TRACKER = 'tracker' as Domain
|
||||
|
||||
@@ -252,7 +252,7 @@ export class TIssue extends TTask implements Issue {
|
||||
*/
|
||||
|
||||
@Model(tracker.class.IssueTemplate, core.class.Doc, DOMAIN_TRACKER)
|
||||
@UX(tracker.string.IssueTemplate, tracker.icon.Issue, 'PROCESS')
|
||||
@UX(tracker.string.IssueTemplate, tracker.icon.IssueTemplates, 'PROCESS')
|
||||
export class TIssueTemplate extends TDoc implements IssueTemplate {
|
||||
@Prop(TypeString(), tracker.string.Title)
|
||||
@Index(IndexKind.FullText)
|
||||
@@ -288,7 +288,7 @@ export class TIssueTemplate extends TDoc implements IssueTemplate {
|
||||
@Prop(ArrOf(TypeRef(tracker.class.IssueTemplate)), tracker.string.IssueTemplate)
|
||||
children!: IssueTemplateChild[]
|
||||
|
||||
@Prop(Collection(chunter.class.Comment), tracker.string.Comments)
|
||||
@Prop(Collection(notification.class.ChatMessage), tracker.string.Comments)
|
||||
comments!: number
|
||||
|
||||
@Prop(Collection(attachment.class.Attachment), tracker.string.Attachments)
|
||||
@@ -336,7 +336,7 @@ export class TComponent extends TDoc implements Component {
|
||||
@Prop(TypeRef(contact.mixin.Employee), tracker.string.ComponentLead)
|
||||
lead!: Ref<Employee> | null
|
||||
|
||||
@Prop(Collection(chunter.class.Comment), chunter.string.Comments)
|
||||
@Prop(Collection(notification.class.ChatMessage), notification.string.Comments)
|
||||
comments!: number
|
||||
|
||||
@Prop(Collection(attachment.class.Attachment), attachment.string.Attachments, { shortLabel: attachment.string.Files })
|
||||
@@ -362,7 +362,7 @@ export class TMilestone extends TDoc implements Milestone {
|
||||
@Index(IndexKind.Indexed)
|
||||
status!: MilestoneStatus
|
||||
|
||||
@Prop(Collection(chunter.class.Comment), chunter.string.Comments)
|
||||
@Prop(Collection(notification.class.ChatMessage), notification.string.Comments)
|
||||
comments!: number
|
||||
|
||||
@Prop(Collection(attachment.class.Attachment), attachment.string.Attachments, { shortLabel: attachment.string.Files })
|
||||
|
||||
@@ -84,8 +84,11 @@ import {
|
||||
type ViewOptionsModel,
|
||||
type Viewlet,
|
||||
type ViewletDescriptor,
|
||||
type ViewletPreference
|
||||
type ViewletPreference,
|
||||
type NotificationAttributePresenter,
|
||||
type ObjectIdentifier
|
||||
} from '@hcengineering/view'
|
||||
|
||||
import view from './plugin'
|
||||
|
||||
export { viewId } from '@hcengineering/view'
|
||||
@@ -124,6 +127,9 @@ export function classPresenter (
|
||||
builder.mixin(_class, core.class.Class, view.mixin.ActivityAttributePresenter, {
|
||||
presenter: activity
|
||||
})
|
||||
builder.mixin(_class, core.class.Class, view.mixin.NotificationAttributePresenter, {
|
||||
presenter: activity
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -204,6 +210,11 @@ export class TActivityAttributePresenter extends TClass implements ActivityAttri
|
||||
presenter!: AnyComponent
|
||||
}
|
||||
|
||||
@Mixin(view.mixin.NotificationAttributePresenter, core.class.Class)
|
||||
export class TNotificationAttributePresenter extends TClass implements NotificationAttributePresenter {
|
||||
presenter!: AnyComponent
|
||||
}
|
||||
|
||||
@Mixin(view.mixin.SpacePresenter, core.class.Class)
|
||||
export class TSpacePresenter extends TClass implements SpacePresenter {
|
||||
presenter!: AnyComponent
|
||||
@@ -258,7 +269,12 @@ export class TObjectFactory extends TClass implements ObjectFactory {
|
||||
|
||||
@Mixin(view.mixin.ObjectTitle, core.class.Class)
|
||||
export class TObjectTitle extends TClass implements ObjectTitle {
|
||||
titleProvider!: Resource<<T extends Doc>(client: Client, ref: Ref<T>) => Promise<string>>
|
||||
titleProvider!: Resource<<T extends Doc>(client: Client, ref: Ref<T>, doc?: T) => Promise<string>>
|
||||
}
|
||||
|
||||
@Mixin(view.mixin.ObjectIdentifier, core.class.Class)
|
||||
export class TObjectIdentifier extends TClass implements ObjectIdentifier {
|
||||
provider!: Resource<<T extends Doc>(client: Client, ref: Ref<T>, doc?: T) => Promise<string>>
|
||||
}
|
||||
|
||||
@Mixin(view.mixin.ListHeaderExtra, core.class.Class)
|
||||
@@ -408,6 +424,7 @@ export function createModel (builder: Builder): void {
|
||||
TAttributePresenter,
|
||||
TAttributeFilterPresenter,
|
||||
TActivityAttributePresenter,
|
||||
TNotificationAttributePresenter,
|
||||
TListItemPresenter,
|
||||
TCollectionEditor,
|
||||
TCollectionPresenter,
|
||||
@@ -436,7 +453,8 @@ export function createModel (builder: Builder): void {
|
||||
TFilteredView,
|
||||
TAllValuesFunc,
|
||||
TAggregation,
|
||||
TGroupping
|
||||
TGroupping,
|
||||
TObjectIdentifier
|
||||
)
|
||||
|
||||
classPresenter(
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
export function groupByArray<T, K> (array: T[], keyProvider: (item: T) => K): Map<K, T[]> {
|
||||
const result = new Map<K, T[]>()
|
||||
|
||||
array.forEach((item) => {
|
||||
const key = keyProvider(item)
|
||||
|
||||
if (!result.has(key)) {
|
||||
result.set(key, [item])
|
||||
} else {
|
||||
result.get(key)?.push(item)
|
||||
}
|
||||
})
|
||||
|
||||
return result
|
||||
}
|
||||
@@ -151,6 +151,12 @@ export class Hierarchy {
|
||||
return data
|
||||
}
|
||||
|
||||
hasClass (_class: Ref<Class<Obj>>): boolean {
|
||||
const data = this.classifiers.get(_class)
|
||||
|
||||
return !(data === undefined || this.isInterface(data))
|
||||
}
|
||||
|
||||
getClassOrInterface (_class: Ref<Class<Obj>>): Class<Obj> {
|
||||
const data = this.classifiers.get(_class)
|
||||
if (data === undefined) {
|
||||
|
||||
@@ -30,3 +30,5 @@ export * from './utils'
|
||||
export * from './backup'
|
||||
export * from './status'
|
||||
export * from './typeof'
|
||||
export * from './common'
|
||||
export * from './time'
|
||||
|
||||
@@ -175,6 +175,14 @@ export abstract class MemDb extends TxProcessor implements Storage {
|
||||
return toFindResult(res, total)
|
||||
}
|
||||
|
||||
async findOne<T extends Doc>(
|
||||
_class: Ref<Class<T>>,
|
||||
query: DocumentQuery<T>,
|
||||
options?: FindOptions<T>
|
||||
): Promise<WithLookup<T> | undefined> {
|
||||
return (await this.findAll(_class, query, { ...options, limit: 1 }))[0]
|
||||
}
|
||||
|
||||
/**
|
||||
* Only in model find without lookups and sorting.
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
function isToday (time: number): boolean {
|
||||
const current = new Date()
|
||||
const target = new Date(time)
|
||||
return (
|
||||
current.getDate() === target.getDate() &&
|
||||
current.getMonth() === target.getMonth() &&
|
||||
current.getFullYear() === target.getFullYear()
|
||||
)
|
||||
}
|
||||
|
||||
export function getDisplayTime (time: number): string {
|
||||
let options: Intl.DateTimeFormatOptions = { hour: 'numeric', minute: 'numeric' }
|
||||
if (!isToday(time)) {
|
||||
options = {
|
||||
month: 'numeric',
|
||||
day: 'numeric',
|
||||
...options
|
||||
}
|
||||
}
|
||||
|
||||
return new Date(time).toLocaleString('default', options)
|
||||
}
|
||||
@@ -1,21 +1,21 @@
|
||||
import core, {
|
||||
Client,
|
||||
Doc,
|
||||
DOMAIN_MIGRATION,
|
||||
Data,
|
||||
Doc,
|
||||
DocumentQuery,
|
||||
Domain,
|
||||
FindOptions,
|
||||
Hierarchy,
|
||||
IncOptions,
|
||||
MigrationState,
|
||||
ModelDb,
|
||||
ObjQueryType,
|
||||
PushOptions,
|
||||
Ref,
|
||||
UnsetOptions,
|
||||
MigrationState,
|
||||
generateId,
|
||||
TxOperations,
|
||||
Data
|
||||
UnsetOptions,
|
||||
generateId
|
||||
} from '@hcengineering/core'
|
||||
import { ModelLogger } from './utils'
|
||||
|
||||
@@ -48,6 +48,14 @@ export type MigrationDocumentQuery<T extends Doc> = {
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface MigrationIterator<T extends Doc> {
|
||||
next: (count: number) => Promise<T[] | null>
|
||||
close: () => Promise<void>
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
* Client to perform model upgrades
|
||||
@@ -62,6 +70,13 @@ export interface MigrationClient {
|
||||
options?: Omit<FindOptions<T>, 'lookup'>
|
||||
) => Promise<T[]>
|
||||
|
||||
// Traverse documents
|
||||
traverse: <T extends Doc>(
|
||||
domain: Domain,
|
||||
query: MigrationDocumentQuery<T>,
|
||||
options?: Omit<FindOptions<T>, 'lookup'>
|
||||
) => Promise<MigrationIterator<T>>
|
||||
|
||||
// Allow to raw update documents inside domain.
|
||||
update: <T extends Doc>(
|
||||
domain: Domain,
|
||||
@@ -77,8 +92,9 @@ export interface MigrationClient {
|
||||
// Move documents per domain
|
||||
move: <T extends Doc>(sourceDomain: Domain, query: DocumentQuery<T>, targetDomain: Domain) => Promise<MigrationResult>
|
||||
|
||||
create: <T extends Doc>(domain: Domain, doc: T) => Promise<void>
|
||||
create: <T extends Doc>(domain: Domain, doc: T | T[]) => Promise<void>
|
||||
delete: <T extends Doc>(domain: Domain, _id: Ref<T>) => Promise<void>
|
||||
deleteMany: <T extends Doc>(domain: Domain, ids: Ref<T>[]) => Promise<void>
|
||||
|
||||
hierarchy: Hierarchy
|
||||
model: ModelDb
|
||||
|
||||
@@ -35,15 +35,20 @@
|
||||
export let kind: 'default' | 'no-border' | 'link' | 'regular' = 'default'
|
||||
export let size: 'small' | 'medium' | 'large' = 'small'
|
||||
export let noShift: boolean = false
|
||||
export let shouldShowAvatar: boolean = true
|
||||
export let accent: boolean = false
|
||||
export let inline: boolean = false
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
type TEdits = 'day' | 'month' | 'year' | 'hour' | 'min'
|
||||
|
||||
interface IEdits {
|
||||
id: TEdits
|
||||
value: number
|
||||
el?: HTMLElement
|
||||
}
|
||||
|
||||
const editsType: TEdits[] = ['day', 'month', 'year', 'hour', 'min']
|
||||
const getIndex = (id: TEdits): number => editsType.indexOf(id)
|
||||
const today = new Date()
|
||||
@@ -297,155 +302,13 @@
|
||||
adaptValue()
|
||||
</script>
|
||||
|
||||
<button
|
||||
bind:this={datePresenter}
|
||||
class="datetime-button {kind} {size}"
|
||||
class:notSelected={!value}
|
||||
class:editable
|
||||
class:edit
|
||||
on:click={() => {
|
||||
if (editable && !opened && withDate) openPopup()
|
||||
else if (editable && !opened && mode === DateRangeMode.TIME) edit = true
|
||||
}}
|
||||
>
|
||||
{#if edit}
|
||||
{#if withDate}
|
||||
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<span
|
||||
bind:this={edits[0].el}
|
||||
class="digit"
|
||||
tabindex="0"
|
||||
on:keydown={(ev) => {
|
||||
keyDown(ev, edits[0].id)
|
||||
}}
|
||||
on:focus={() => {
|
||||
focused(edits[0].id)
|
||||
}}
|
||||
on:blur={(ev) => {
|
||||
unfocus(ev, edits[0].id)
|
||||
}}
|
||||
>
|
||||
{#if edits[0].value > -1}
|
||||
{edits[0].value.toString().padStart(2, '0')}
|
||||
{:else}<Label label={ui.string.DD} />{/if}
|
||||
</span>
|
||||
<span class="separator">.</span>
|
||||
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<span
|
||||
bind:this={edits[1].el}
|
||||
class="digit"
|
||||
tabindex="0"
|
||||
on:keydown={(ev) => {
|
||||
keyDown(ev, edits[1].id)
|
||||
}}
|
||||
on:focus={() => {
|
||||
focused(edits[1].id)
|
||||
}}
|
||||
on:blur={(ev) => {
|
||||
unfocus(ev, edits[1].id)
|
||||
}}
|
||||
>
|
||||
{#if edits[1].value > -1}
|
||||
{edits[1].value.toString().padStart(2, '0')}
|
||||
{:else}<Label label={ui.string.MM} />{/if}
|
||||
</span>
|
||||
<span class="separator">.</span>
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
|
||||
<span
|
||||
bind:this={edits[2].el}
|
||||
class="digit"
|
||||
tabindex="0"
|
||||
on:keydown={(ev) => {
|
||||
keyDown(ev, edits[2].id)
|
||||
}}
|
||||
on:focus={() => {
|
||||
focused(edits[2].id)
|
||||
}}
|
||||
on:blur={(ev) => {
|
||||
unfocus(ev, edits[2].id)
|
||||
}}
|
||||
>
|
||||
{#if edits[2].value > -1}
|
||||
{edits[2].value.toString().padStart(4, '0')}
|
||||
{:else}<Label label={ui.string.YYYY} />{/if}
|
||||
</span>
|
||||
{/if}
|
||||
{#if withTime}
|
||||
{#if mode === DateRangeMode.DATETIME}
|
||||
<div class="time-divider" />
|
||||
{/if}
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
|
||||
<span
|
||||
bind:this={edits[3].el}
|
||||
class="digit"
|
||||
tabindex="0"
|
||||
on:keydown={(ev) => {
|
||||
keyDown(ev, edits[3].id)
|
||||
}}
|
||||
on:focus={() => {
|
||||
focused(edits[3].id)
|
||||
}}
|
||||
on:blur={(ev) => {
|
||||
unfocus(ev, edits[3].id)
|
||||
}}
|
||||
>
|
||||
{#if edits[3].value > -1}
|
||||
{edits[3].value.toString().padStart(2, '0')}
|
||||
{:else}<Label label={ui.string.HH} />{/if}
|
||||
</span>
|
||||
<span class="separator">:</span>
|
||||
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<span
|
||||
bind:this={edits[4].el}
|
||||
class="digit"
|
||||
tabindex="0"
|
||||
on:keydown={(ev) => {
|
||||
keyDown(ev, edits[4].id)
|
||||
}}
|
||||
on:focus={() => {
|
||||
focused(edits[4].id)
|
||||
}}
|
||||
on:blur={(ev) => {
|
||||
unfocus(ev, edits[4].id)
|
||||
}}
|
||||
>
|
||||
{#if edits[4].value > -1}
|
||||
{edits[4].value.toString().padStart(2, '0')}
|
||||
{:else}<Label label={ui.string.MM} />{/if}
|
||||
</span>
|
||||
{/if}
|
||||
{#if value}
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
|
||||
<div
|
||||
bind:this={closeBtn}
|
||||
class="close-btn"
|
||||
tabindex="0"
|
||||
on:click={() => {
|
||||
selected = defaultSelected
|
||||
startTyping = true
|
||||
value = null
|
||||
setEmptyEdits()
|
||||
const newFocusElement = edits[mode === DateRangeMode.TIME ? 2 : 0].el
|
||||
if (newFocusElement) newFocusElement.focus()
|
||||
}}
|
||||
on:blur={(ev) => {
|
||||
unfocus(ev, closeBtn)
|
||||
}}
|
||||
>
|
||||
<Icon icon={IconClose} size={'x-small'} />
|
||||
{#if inline}
|
||||
<span class:fs-bold={accent}>
|
||||
{#if shouldShowAvatar}
|
||||
<div class="btn-icon {iconModifier}">
|
||||
<Icon icon={iconModifier === 'overdue' && !shouldIgnoreOverdue ? DPCalendarOver : DPCalendar} size={'full'} />
|
||||
</div>
|
||||
{/if}
|
||||
{:else}
|
||||
<div class="btn-icon {iconModifier}">
|
||||
<Icon icon={iconModifier === 'overdue' && !shouldIgnoreOverdue ? DPCalendarOver : DPCalendar} size={'full'} />
|
||||
</div>
|
||||
{#if value !== undefined && value !== null && value.toString() !== ''}
|
||||
{#if withDate}
|
||||
{new Date(value).getDate()}
|
||||
@@ -467,8 +330,181 @@
|
||||
<Label label={labelNull} />
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
</button>
|
||||
</span>
|
||||
{:else}
|
||||
<button
|
||||
bind:this={datePresenter}
|
||||
class="datetime-button {kind} {size}"
|
||||
class:notSelected={!value}
|
||||
class:editable
|
||||
class:edit
|
||||
on:click={() => {
|
||||
if (editable && !opened && withDate) openPopup()
|
||||
else if (editable && !opened && mode === DateRangeMode.TIME) edit = true
|
||||
}}
|
||||
>
|
||||
{#if edit}
|
||||
{#if withDate}
|
||||
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<span
|
||||
bind:this={edits[0].el}
|
||||
class="digit"
|
||||
tabindex="0"
|
||||
on:keydown={(ev) => {
|
||||
keyDown(ev, edits[0].id)
|
||||
}}
|
||||
on:focus={() => {
|
||||
focused(edits[0].id)
|
||||
}}
|
||||
on:blur={(ev) => {
|
||||
unfocus(ev, edits[0].id)
|
||||
}}
|
||||
>
|
||||
{#if edits[0].value > -1}
|
||||
{edits[0].value.toString().padStart(2, '0')}
|
||||
{:else}<Label label={ui.string.DD} />{/if}
|
||||
</span>
|
||||
<span class="separator">.</span>
|
||||
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<span
|
||||
bind:this={edits[1].el}
|
||||
class="digit"
|
||||
tabindex="0"
|
||||
on:keydown={(ev) => {
|
||||
keyDown(ev, edits[1].id)
|
||||
}}
|
||||
on:focus={() => {
|
||||
focused(edits[1].id)
|
||||
}}
|
||||
on:blur={(ev) => {
|
||||
unfocus(ev, edits[1].id)
|
||||
}}
|
||||
>
|
||||
{#if edits[1].value > -1}
|
||||
{edits[1].value.toString().padStart(2, '0')}
|
||||
{:else}<Label label={ui.string.MM} />{/if}
|
||||
</span>
|
||||
<span class="separator">.</span>
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
|
||||
<span
|
||||
bind:this={edits[2].el}
|
||||
class="digit"
|
||||
tabindex="0"
|
||||
on:keydown={(ev) => {
|
||||
keyDown(ev, edits[2].id)
|
||||
}}
|
||||
on:focus={() => {
|
||||
focused(edits[2].id)
|
||||
}}
|
||||
on:blur={(ev) => {
|
||||
unfocus(ev, edits[2].id)
|
||||
}}
|
||||
>
|
||||
{#if edits[2].value > -1}
|
||||
{edits[2].value.toString().padStart(4, '0')}
|
||||
{:else}<Label label={ui.string.YYYY} />{/if}
|
||||
</span>
|
||||
{/if}
|
||||
{#if withTime}
|
||||
{#if mode === DateRangeMode.DATETIME}
|
||||
<div class="time-divider" />
|
||||
{/if}
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
|
||||
<span
|
||||
bind:this={edits[3].el}
|
||||
class="digit"
|
||||
tabindex="0"
|
||||
on:keydown={(ev) => {
|
||||
keyDown(ev, edits[3].id)
|
||||
}}
|
||||
on:focus={() => {
|
||||
focused(edits[3].id)
|
||||
}}
|
||||
on:blur={(ev) => {
|
||||
unfocus(ev, edits[3].id)
|
||||
}}
|
||||
>
|
||||
{#if edits[3].value > -1}
|
||||
{edits[3].value.toString().padStart(2, '0')}
|
||||
{:else}<Label label={ui.string.HH} />{/if}
|
||||
</span>
|
||||
<span class="separator">:</span>
|
||||
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<span
|
||||
bind:this={edits[4].el}
|
||||
class="digit"
|
||||
tabindex="0"
|
||||
on:keydown={(ev) => {
|
||||
keyDown(ev, edits[4].id)
|
||||
}}
|
||||
on:focus={() => {
|
||||
focused(edits[4].id)
|
||||
}}
|
||||
on:blur={(ev) => {
|
||||
unfocus(ev, edits[4].id)
|
||||
}}
|
||||
>
|
||||
{#if edits[4].value > -1}
|
||||
{edits[4].value.toString().padStart(2, '0')}
|
||||
{:else}<Label label={ui.string.MM} />{/if}
|
||||
</span>
|
||||
{/if}
|
||||
{#if value}
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
|
||||
<div
|
||||
bind:this={closeBtn}
|
||||
class="close-btn"
|
||||
tabindex="0"
|
||||
on:click={() => {
|
||||
selected = defaultSelected
|
||||
startTyping = true
|
||||
value = null
|
||||
setEmptyEdits()
|
||||
const newFocusElement = edits[mode === DateRangeMode.TIME ? 2 : 0].el
|
||||
if (newFocusElement) newFocusElement.focus()
|
||||
}}
|
||||
on:blur={(ev) => {
|
||||
unfocus(ev, closeBtn)
|
||||
}}
|
||||
>
|
||||
<Icon icon={IconClose} size={'x-small'} />
|
||||
</div>
|
||||
{/if}
|
||||
{:else}
|
||||
<div class="btn-icon {iconModifier}">
|
||||
<Icon icon={iconModifier === 'overdue' && !shouldIgnoreOverdue ? DPCalendarOver : DPCalendar} size={'full'} />
|
||||
</div>
|
||||
{#if value !== undefined && value !== null && value.toString() !== ''}
|
||||
{#if withDate}
|
||||
{new Date(value).getDate()}
|
||||
{getMonthName(new Date(value), 'short')}
|
||||
{#if new Date(value).getFullYear() !== today.getFullYear()}
|
||||
{new Date(value).getFullYear()}
|
||||
{/if}
|
||||
{/if}
|
||||
{#if withTime}
|
||||
{#if withDate}
|
||||
<div class="time-divider" />
|
||||
{/if}
|
||||
{new Date(value).getHours().toString().padStart(2, '0')}
|
||||
<span class="separator">:</span>
|
||||
{new Date(value).getMinutes().toString().padStart(2, '0')}
|
||||
{/if}
|
||||
{:else}
|
||||
<div class="overflow-label">
|
||||
<Label label={labelNull} />
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
</button>
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
.datetime-button {
|
||||
@@ -490,18 +526,23 @@
|
||||
transition-duration: 0.15s;
|
||||
cursor: pointer;
|
||||
|
||||
&.noPadding {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
&.small {
|
||||
height: 1.5rem;
|
||||
}
|
||||
|
||||
&.medium {
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
&.large {
|
||||
height: 2.25rem;
|
||||
}
|
||||
|
||||
.btn-icon {
|
||||
margin-right: 0.375rem;
|
||||
width: 0.875rem;
|
||||
height: 0.875rem;
|
||||
transition: color 0.15s;
|
||||
@@ -510,12 +551,15 @@
|
||||
&.normal {
|
||||
color: var(--theme-content-color);
|
||||
}
|
||||
|
||||
&.warning {
|
||||
color: var(--theme-warning-color);
|
||||
}
|
||||
|
||||
&.overdue {
|
||||
color: var(--theme-error-color);
|
||||
}
|
||||
|
||||
&.critical {
|
||||
color: var(--theme-error-color);
|
||||
}
|
||||
@@ -529,6 +573,7 @@
|
||||
color: var(--theme-caption-color);
|
||||
}
|
||||
}
|
||||
|
||||
&.no-border {
|
||||
font-weight: 400;
|
||||
color: var(--theme-content-color);
|
||||
@@ -544,6 +589,7 @@
|
||||
color: var(--theme-caption-color);
|
||||
}
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
color: var(--theme-trans-color);
|
||||
background-color: var(--theme-button-disabled);
|
||||
@@ -552,8 +598,10 @@
|
||||
.btn-icon {
|
||||
color: var(--theme-trans-color);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: var(--theme-trans-color);
|
||||
|
||||
.btn-icon {
|
||||
color: var(--theme-trans-color);
|
||||
}
|
||||
@@ -565,27 +613,34 @@
|
||||
|
||||
&:hover {
|
||||
background-color: var(--theme-button-hovered);
|
||||
|
||||
.btn-icon {
|
||||
&.normal {
|
||||
color: var(--caption-color);
|
||||
}
|
||||
|
||||
&.warning {
|
||||
color: var(--theme-warning-color);
|
||||
}
|
||||
|
||||
&.overdue {
|
||||
color: var(--theme-error-color);
|
||||
}
|
||||
|
||||
&.critical {
|
||||
color: var(--theme-error-color);
|
||||
}
|
||||
}
|
||||
|
||||
.time-divider {
|
||||
background-color: var(--theme-divider-color);
|
||||
}
|
||||
}
|
||||
|
||||
&:focus-within {
|
||||
background-color: var(--theme-button-focused);
|
||||
border-color: var(--primary-edit-border-color);
|
||||
|
||||
&:hover {
|
||||
background-color: var(--theme-button-hovered);
|
||||
}
|
||||
@@ -596,6 +651,7 @@
|
||||
padding: 0 0.125rem;
|
||||
background-color: transparent;
|
||||
border-color: var(--primary-edit-border-color);
|
||||
|
||||
&:hover {
|
||||
background-color: transparent;
|
||||
}
|
||||
@@ -606,13 +662,16 @@
|
||||
padding: 0 0.875rem;
|
||||
width: 100%;
|
||||
color: var(--theme-caption-color);
|
||||
|
||||
&:hover {
|
||||
background-color: var(--theme-bg-color);
|
||||
border-color: var(--theme-divider-color);
|
||||
|
||||
.btn-icon {
|
||||
color: var(--theme-content-color);
|
||||
}
|
||||
}
|
||||
|
||||
&.edit {
|
||||
padding: 0 0.5rem;
|
||||
}
|
||||
@@ -627,13 +686,16 @@
|
||||
.btn-icon {
|
||||
color: var(--theme-content-color);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: var(--theme-button-hovered);
|
||||
border-color: var(--theme-divider-color);
|
||||
|
||||
.btn-icon {
|
||||
color: var(--theme-content-color);
|
||||
}
|
||||
}
|
||||
|
||||
// &.edit {
|
||||
// padding: 0 0.5rem;
|
||||
// }
|
||||
@@ -670,6 +732,7 @@
|
||||
&:focus {
|
||||
background-color: var(--primary-bg-color);
|
||||
}
|
||||
|
||||
&::after {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
@@ -680,6 +743,7 @@
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.time-divider {
|
||||
flex-shrink: 0;
|
||||
margin: 0 0.25rem;
|
||||
@@ -688,15 +752,18 @@
|
||||
height: 0.75rem;
|
||||
background-color: var(--theme-divider-color);
|
||||
}
|
||||
|
||||
.separator {
|
||||
margin: 0 0.1rem;
|
||||
}
|
||||
|
||||
&.notSelected {
|
||||
color: var(--theme-dark-color);
|
||||
|
||||
.btn-icon {
|
||||
color: var(--theme-darker-color);
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&:hover .btn-icon {
|
||||
color: var(--theme-content-color);
|
||||
|
||||
@@ -1,21 +1,17 @@
|
||||
{
|
||||
"string": {
|
||||
"Delete": "Delete",
|
||||
"Edit": "Edit",
|
||||
"Edited": "edited",
|
||||
"DocCreated": "created {_class}",
|
||||
"DocAdded": "added {_class}",
|
||||
"DocDeleted": "deleted {_class}",
|
||||
"Activity": "Activity",
|
||||
"Changed": "changed",
|
||||
"To": "to",
|
||||
"Unset": "unset",
|
||||
"CollectionUpdated": "Update {collection}",
|
||||
"Added": "added",
|
||||
"Removed": "removed",
|
||||
"From": "from",
|
||||
"All": "All",
|
||||
"Attributes": "Attributes",
|
||||
"NewestFirst": "Newest first"
|
||||
"Changed": "changed",
|
||||
"CollectionUpdated": "Update {collection}",
|
||||
"DocAdded": "added {_class}",
|
||||
"DocCreated": "created {_class}",
|
||||
"DocDeleted": "deleted {_class}",
|
||||
"Edited": "edited",
|
||||
"From": "from",
|
||||
"Removed": "removed",
|
||||
"To": "to",
|
||||
"Unset": "unset"
|
||||
}
|
||||
}
|
||||
@@ -1,21 +1,17 @@
|
||||
{
|
||||
"string": {
|
||||
"Delete": "Удалить",
|
||||
"Edit": "Редактировать",
|
||||
"Edited": "отредактировал(а)",
|
||||
"DocCreated": "создал(а) {_class}",
|
||||
"DocAdded": "добавил(а) {_class}",
|
||||
"DocDeleted": "удалил(а) {_class}",
|
||||
"Activity": "Активность",
|
||||
"Changed": "изменил(а)",
|
||||
"To": "на",
|
||||
"Unset": "сбросил",
|
||||
"CollectionUpdated": "Обновлена {collection}",
|
||||
"Added": "добавила(а)",
|
||||
"Removed": "удалил(а)",
|
||||
"From": "из",
|
||||
"All": "Все",
|
||||
"Attributes": "Атрибуты",
|
||||
"NewestFirst": "Сначала новые"
|
||||
"Changed": "изменил(а)",
|
||||
"CollectionUpdated": "Обновлена {collection}",
|
||||
"DocAdded": "добавил(а) {_class}",
|
||||
"DocCreated": "создал(а) {_class}",
|
||||
"DocDeleted": "удалил(а) {_class}",
|
||||
"Edited": "отредактировал(а)",
|
||||
"From": "из",
|
||||
"Removed": "удалил(а)",
|
||||
"To": "на",
|
||||
"Unset": "сбросил"
|
||||
}
|
||||
}
|
||||
@@ -13,39 +13,36 @@
|
||||
"build:watch": "tsc --noEmit --outDir ./dist_cache"
|
||||
},
|
||||
"devDependencies": {
|
||||
"svelte-loader": "^3.1.9",
|
||||
"sass": "^1.53.0",
|
||||
"svelte-preprocess": "^5.1.0",
|
||||
"@hcengineering/platform-rig": "^0.6.0",
|
||||
"@types/jest": "^29.5.5",
|
||||
"@typescript-eslint/eslint-plugin": "^6.11.0",
|
||||
"@typescript-eslint/parser": "^6.11.0",
|
||||
"eslint": "^8.54.0",
|
||||
"eslint-config-standard-with-typescript": "^40.0.0",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-n": "^15.4.0",
|
||||
"eslint-plugin-promise": "^6.1.1",
|
||||
"eslint-plugin-svelte": "^2.34.0",
|
||||
"prettier-plugin-svelte": "^3.1.0",
|
||||
"eslint": "^8.54.0",
|
||||
"prettier": "^3.1.0",
|
||||
"svelte-check": "^3.6.0",
|
||||
"typescript": "^5.2.2",
|
||||
"jest": "^29.7.0",
|
||||
"prettier": "^3.1.0",
|
||||
"prettier-plugin-svelte": "^3.1.0",
|
||||
"sass": "^1.53.0",
|
||||
"svelte-check": "^3.6.0",
|
||||
"svelte-eslint-parser": "^0.33.1",
|
||||
"svelte-loader": "^3.1.9",
|
||||
"svelte-preprocess": "^5.1.0",
|
||||
"ts-jest": "^29.1.1",
|
||||
"@types/jest": "^29.5.5",
|
||||
"svelte-eslint-parser": "^0.33.1"
|
||||
"typescript": "^5.2.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hcengineering/core": "^0.6.28",
|
||||
"@hcengineering/platform": "^0.6.9",
|
||||
"@hcengineering/ui": "^0.6.11",
|
||||
"@hcengineering/presentation": "^0.6.2",
|
||||
"@hcengineering/activity": "^0.6.0",
|
||||
"svelte": "^4.2.5",
|
||||
"@hcengineering/text-editor": "^0.6.0",
|
||||
"@hcengineering/contact": "^0.6.20",
|
||||
"@hcengineering/core": "^0.6.28",
|
||||
"@hcengineering/notification": "^0.6.16",
|
||||
"@hcengineering/platform": "^0.6.9",
|
||||
"@hcengineering/presentation": "^0.6.2",
|
||||
"@hcengineering/ui": "^0.6.11",
|
||||
"@hcengineering/view": "^0.6.9",
|
||||
"@hcengineering/view-resources": "^0.6.0",
|
||||
"@hcengineering/attachment": "^0.6.9"
|
||||
"svelte": "^4.2.5"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,24 +1,16 @@
|
||||
import { type DisplayTx } from '@hcengineering/activity'
|
||||
import core, {
|
||||
type AnyAttribute,
|
||||
type AttachedDoc,
|
||||
type Attribute,
|
||||
type Class,
|
||||
type Client,
|
||||
type Collection,
|
||||
type Doc,
|
||||
type Hierarchy,
|
||||
type Ref,
|
||||
SortingOrder,
|
||||
type Tx,
|
||||
type TxCollectionCUD,
|
||||
type TxCreateDoc,
|
||||
type TxCUD,
|
||||
type TxMixin,
|
||||
TxProcessor,
|
||||
type TxUpdateDoc
|
||||
} from '@hcengineering/core'
|
||||
import { createQuery, type LiveQuery } from '@hcengineering/presentation'
|
||||
|
||||
/**
|
||||
* @public
|
||||
@@ -32,415 +24,6 @@ export function activityKey (objectClass: Ref<Class<Doc>>, txClass: Ref<Class<Tx
|
||||
return objectClass + ':' + txClass
|
||||
}
|
||||
|
||||
function isEqualOps (op1: any, op2: any): boolean {
|
||||
if (typeof op1 === 'string' && typeof op2 === 'string') {
|
||||
return op1 === op2
|
||||
}
|
||||
if (typeof op1 !== typeof op2) {
|
||||
return false
|
||||
}
|
||||
const o1 = Object.keys(op1).sort().join('-')
|
||||
const o2 = Object.keys(op2).sort().join('-')
|
||||
return o1 === o2
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export type DisplayTxListener = (objectId: Ref<Doc>, txes: DisplayTx[]) => void
|
||||
|
||||
// Use 5 minutes to combine similar transactions.
|
||||
const combineThreshold = 5 * 60 * 1000
|
||||
const createCombineThreshold = 10 * 1000
|
||||
|
||||
/**
|
||||
* Define activity.
|
||||
*
|
||||
* Allow to recieve a list of transactions and notify client about it.
|
||||
*/
|
||||
export interface Activity {
|
||||
update: (
|
||||
objectId: Ref<Doc>,
|
||||
objectClass: Ref<Class<Doc>>,
|
||||
listener: DisplayTxListener,
|
||||
sort: SortingOrder,
|
||||
editable: Map<Ref<Class<Doc>>, boolean>
|
||||
) => boolean
|
||||
}
|
||||
|
||||
class ActivityImpl implements Activity {
|
||||
private readonly ownTxQuery: LiveQuery
|
||||
private readonly attachedTxQuery: LiveQuery
|
||||
private readonly attachedChangeTxQuery: LiveQuery
|
||||
private readonly hiddenAttributes: Set<string>
|
||||
private prevObjectId: Ref<Doc> | undefined
|
||||
private prevObjectClass: Ref<Class<Doc>> | undefined
|
||||
private prevSort: SortingOrder | undefined
|
||||
private editable: Map<Ref<Class<Doc>>, boolean> | undefined
|
||||
|
||||
private ownTxes: Array<TxCUD<Doc>> = []
|
||||
private attachedTxes: Array<TxCollectionCUD<Doc, AttachedDoc>> = []
|
||||
private attacheChangedTxes: Array<TxCollectionCUD<Doc, AttachedDoc>> = []
|
||||
private readonly hierarchy: Hierarchy
|
||||
constructor (
|
||||
readonly client: Client,
|
||||
attributes: Map<string, AnyAttribute>
|
||||
) {
|
||||
this.hierarchy = client.getHierarchy()
|
||||
this.hiddenAttributes = new Set(
|
||||
[...attributes.entries()].filter(([, value]) => value.hidden === true).map(([key]) => key)
|
||||
)
|
||||
this.ownTxQuery = createQuery()
|
||||
this.attachedTxQuery = createQuery()
|
||||
this.attachedChangeTxQuery = createQuery()
|
||||
}
|
||||
|
||||
private notify (objectId: Ref<Doc>, listener: DisplayTxListener, sort: SortingOrder): void {
|
||||
if (this.editable != null) {
|
||||
this.combineTransactions(objectId, this.ownTxes, this.attachedTxes, this.attacheChangedTxes, this.editable).then(
|
||||
(result) => {
|
||||
const sorted = result.sort((a, b) => (a.tx.modifiedOn - b.tx.modifiedOn) * sort)
|
||||
listener(objectId, sorted)
|
||||
},
|
||||
(err) => {
|
||||
console.error(err)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
update (
|
||||
objectId: Ref<Doc>,
|
||||
objectClass: Ref<Class<Doc>>,
|
||||
listener: DisplayTxListener,
|
||||
sort: SortingOrder,
|
||||
editable: Map<Ref<Class<Doc>>, boolean>
|
||||
): boolean {
|
||||
this.editable = editable
|
||||
if (objectId === this.prevObjectId && objectClass === this.prevObjectClass && sort === this.prevSort) {
|
||||
return false
|
||||
}
|
||||
this.prevObjectClass = objectClass
|
||||
this.prevObjectId = objectId
|
||||
this.prevSort = sort
|
||||
let isAttached = false
|
||||
|
||||
isAttached = this.hierarchy.isDerived(objectClass, core.class.AttachedDoc)
|
||||
|
||||
this.ownTxQuery.query<TxCUD<Doc>>(
|
||||
isAttached ? core.class.TxCollectionCUD : core.class.TxCUD,
|
||||
isAttached
|
||||
? { 'tx.objectId': objectId as Ref<AttachedDoc> }
|
||||
: {
|
||||
objectId,
|
||||
_class: {
|
||||
$in: [core.class.TxCreateDoc, core.class.TxUpdateDoc, core.class.TxRemoveDoc, core.class.TxMixin]
|
||||
}
|
||||
},
|
||||
(result) => {
|
||||
this.ownTxes = result
|
||||
this.notify(objectId, listener, sort)
|
||||
},
|
||||
{ sort: { modifiedOn: SortingOrder.Ascending } }
|
||||
)
|
||||
|
||||
this.attachedTxQuery.query<TxCollectionCUD<Doc, AttachedDoc>>(
|
||||
core.class.TxCollectionCUD,
|
||||
{
|
||||
objectId,
|
||||
'tx._class': { $in: [core.class.TxCreateDoc, core.class.TxUpdateDoc, core.class.TxRemoveDoc] }
|
||||
},
|
||||
(result) => {
|
||||
this.attachedTxes = result
|
||||
this.notify(objectId, listener, sort)
|
||||
},
|
||||
{ sort: { modifiedOn: SortingOrder.Ascending } }
|
||||
)
|
||||
|
||||
this.attachedChangeTxQuery.query<TxCollectionCUD<Doc, AttachedDoc>>(
|
||||
core.class.TxCollectionCUD,
|
||||
{
|
||||
'tx.operations.attachedTo': objectId,
|
||||
'tx._class': core.class.TxUpdateDoc
|
||||
},
|
||||
(result) => {
|
||||
this.attacheChangedTxes = result
|
||||
this.notify(objectId, listener, sort)
|
||||
},
|
||||
{ sort: { modifiedOn: SortingOrder.Ascending } }
|
||||
)
|
||||
// In case editable is changed
|
||||
this.notify(objectId, listener, sort)
|
||||
return true
|
||||
}
|
||||
|
||||
async combineTransactions (
|
||||
_id: Ref<Doc>,
|
||||
ownTxes: Array<TxCUD<Doc>>,
|
||||
attachedTxes: Array<TxCollectionCUD<Doc, AttachedDoc>>,
|
||||
attachedChangeTxes: Array<TxCollectionCUD<Doc, AttachedDoc>>,
|
||||
editable: Map<Ref<Class<Doc>>, boolean>
|
||||
): Promise<DisplayTx[]> {
|
||||
const parents = new Map<Ref<Doc>, DisplayTx>()
|
||||
|
||||
let results: DisplayTx[] = []
|
||||
|
||||
for (const tx of ownTxes) {
|
||||
if (!this.filterUpdateTx(tx)) continue
|
||||
const [result] = this.createDisplayTx(tx, parents, true)
|
||||
// Combine previous update transaction for same field and if same operation and time treshold is ok
|
||||
results = this.integrateTxWithResults(results, result, editable)
|
||||
this.updateRemovedState(result, results)
|
||||
}
|
||||
|
||||
for (let tx of Array.from(attachedTxes)
|
||||
.concat(attachedChangeTxes)
|
||||
.sort((a, b) => a.modifiedOn - b.modifiedOn)) {
|
||||
if (!this.filterUpdateTx(tx)) continue
|
||||
const changeAttached = this.isChangeAttachedTx(tx)
|
||||
if (changeAttached || this.isDisplayTxRequired(tx)) {
|
||||
if (changeAttached) {
|
||||
tx = await this.createFakeTx(_id, tx)
|
||||
}
|
||||
const [result, isUpdated, isMixin] = this.createDisplayTx(tx, parents, false)
|
||||
if (!(isUpdated || isMixin)) {
|
||||
// Combine previous update transaction for same field and if same operation and time treshold is ok
|
||||
results = this.integrateTxWithResults(results, result, editable)
|
||||
this.updateRemovedState(result, results)
|
||||
}
|
||||
}
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
private async createFakeTx (
|
||||
_id: Ref<Doc>,
|
||||
cltx: TxCollectionCUD<Doc, AttachedDoc>
|
||||
): Promise<TxCollectionCUD<Doc, AttachedDoc>> {
|
||||
if (_id === cltx.objectId) {
|
||||
cltx.tx._class = core.class.TxRemoveDoc
|
||||
} else {
|
||||
const createTx = await this.client.findOne(core.class.TxCollectionCUD, {
|
||||
'tx.objectId': cltx.tx.objectId,
|
||||
'tx._class': core.class.TxCreateDoc
|
||||
})
|
||||
if (createTx !== undefined) {
|
||||
cltx.tx = createTx.tx
|
||||
cltx.tx.modifiedBy = cltx.modifiedBy
|
||||
cltx.tx.modifiedOn = cltx.modifiedOn
|
||||
}
|
||||
}
|
||||
return cltx
|
||||
}
|
||||
|
||||
private isChangeAttachedTx (cltx: TxCollectionCUD<Doc, AttachedDoc>): boolean {
|
||||
const tx = TxProcessor.extractTx(cltx)
|
||||
if (this.hierarchy.isDerived(tx._class, core.class.TxUpdateDoc)) {
|
||||
const utx = tx as TxUpdateDoc<AttachedDoc>
|
||||
return utx.operations.attachedTo !== undefined
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private updateRemovedState (result: DisplayTx, results: DisplayTx[]): void {
|
||||
if (result.removed) {
|
||||
// We need to mark all transactions for same object as removed as well.
|
||||
for (const t of results) {
|
||||
if (t.tx.objectId === result.tx.objectId) {
|
||||
t.removed = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private isDisplayTxRequired (cltx: TxCollectionCUD<Doc, AttachedDoc>): boolean {
|
||||
// Check if collection attribute is hidden
|
||||
if (this.hiddenAttributes.has(cltx.collection)) {
|
||||
return false
|
||||
}
|
||||
const tx = TxProcessor.extractTx(cltx)
|
||||
if (
|
||||
[core.class.TxCreateDoc, core.class.TxUpdateDoc, core.class.TxRemoveDoc, core.class.TxMixin].includes(tx._class)
|
||||
) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private readonly getUpdateTx = (tx: TxCUD<Doc>): TxUpdateDoc<Doc> | undefined => {
|
||||
if (this.hierarchy.isDerived(tx._class, core.class.TxCollectionCUD)) {
|
||||
const cltx = tx as TxCollectionCUD<Doc, AttachedDoc>
|
||||
tx = TxProcessor.extractTx(cltx) as TxCUD<Doc>
|
||||
}
|
||||
|
||||
if (tx._class !== core.class.TxUpdateDoc) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
return tx as TxUpdateDoc<Doc>
|
||||
}
|
||||
|
||||
filterUpdateTx (tx: TxCUD<Doc>): boolean {
|
||||
const utx = this.getUpdateTx(tx)
|
||||
|
||||
if (utx === undefined) {
|
||||
return true
|
||||
}
|
||||
|
||||
const ops = Object.keys(utx.operations)
|
||||
|
||||
if (ops.length > 1) {
|
||||
return true
|
||||
}
|
||||
|
||||
return !this.hiddenAttributes.has(ops[0])
|
||||
}
|
||||
|
||||
createDisplayTx (tx: TxCUD<Doc>, parents: Map<Ref<Doc>, DisplayTx>, isOwnTx: boolean): [DisplayTx, boolean, boolean] {
|
||||
let collectionAttribute: Attribute<Collection<AttachedDoc>> | undefined
|
||||
const originTx = tx
|
||||
if (this.hierarchy.isDerived(tx._class, core.class.TxCollectionCUD)) {
|
||||
const cltx = tx as TxCollectionCUD<Doc, AttachedDoc>
|
||||
tx = TxProcessor.extractTx(cltx) as TxCUD<Doc>
|
||||
|
||||
// Check mixin classes for desired attribute
|
||||
for (const cl of this.hierarchy.getDescendants(cltx.objectClass)) {
|
||||
try {
|
||||
collectionAttribute = this.hierarchy.findAttribute(cl, cltx.collection) as Attribute<Collection<AttachedDoc>>
|
||||
if (collectionAttribute !== undefined) {
|
||||
break
|
||||
}
|
||||
} catch (err: any) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
let firstTx = parents.get(tx.objectId)
|
||||
const result: DisplayTx = newDisplayTx(tx, this.hierarchy, isOwnTx, originTx)
|
||||
|
||||
result.collectionAttribute = collectionAttribute
|
||||
|
||||
result.doc = firstTx?.doc ?? result.doc
|
||||
result.prevDoc = this.hierarchy.clone(result.doc)
|
||||
|
||||
firstTx = firstTx ?? result
|
||||
parents.set(tx.objectId, firstTx)
|
||||
// If we have updates also apply them all.
|
||||
const isUpdated = this.checkUpdateState(result, firstTx)
|
||||
const isMixin = this.checkMixinState(result, firstTx)
|
||||
|
||||
this.checkRemoveState(tx, firstTx, result)
|
||||
return [result, isUpdated, isMixin]
|
||||
}
|
||||
|
||||
private checkRemoveState (tx: TxCUD<Doc>, firstTx: DisplayTx, result: DisplayTx): void {
|
||||
if (this.hierarchy.isDerived(tx._class, core.class.TxRemoveDoc)) {
|
||||
firstTx.removed = true
|
||||
result.removed = true
|
||||
}
|
||||
}
|
||||
|
||||
checkUpdateState (result: DisplayTx, firstTx: DisplayTx): boolean {
|
||||
if (this.hierarchy.isDerived(result.tx._class, core.class.TxUpdateDoc) && result.doc !== undefined) {
|
||||
firstTx.doc = TxProcessor.updateDoc2Doc(result.doc, result.tx as TxUpdateDoc<Doc>)
|
||||
firstTx.updated = true
|
||||
result.updated = true
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
checkMixinState (result: DisplayTx, firstTx: DisplayTx): boolean {
|
||||
if (this.hierarchy.isDerived(result.tx._class, core.class.TxMixin) && result.doc !== undefined) {
|
||||
const mix = result.tx as TxMixin<Doc, Doc>
|
||||
firstTx.doc = TxProcessor.updateMixin4Doc(result.doc, mix)
|
||||
firstTx.mixin = true
|
||||
result.mixin = true
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
integrateTxWithResults (
|
||||
results: DisplayTx[],
|
||||
result: DisplayTx,
|
||||
editable: Map<Ref<Class<Doc>>, boolean>
|
||||
): DisplayTx[] {
|
||||
const curUpdate: any = getCombineOpFromTx(result)
|
||||
|
||||
if (curUpdate === undefined || (result.doc !== undefined && editable.get(result.doc._class) === true)) {
|
||||
results.push(result)
|
||||
return results
|
||||
}
|
||||
const newResults = results.filter((prevTx) => {
|
||||
const prevUpdate: any = getCombineOpFromTx(prevTx)
|
||||
if (this.isInitTx(prevTx, result)) {
|
||||
result = prevTx
|
||||
return false
|
||||
}
|
||||
|
||||
// If same tx or same collection
|
||||
if (this.isSameKindTx(prevTx, result) || prevUpdate === curUpdate) {
|
||||
if (result.tx.modifiedOn - prevTx.tx.modifiedOn < combineThreshold && isEqualOps(prevUpdate, curUpdate)) {
|
||||
// we have same keys,
|
||||
// Remember previous transactions
|
||||
if (result.txDocIds === undefined) {
|
||||
result.txDocIds = new Set(prevTx.txDocIds)
|
||||
}
|
||||
if (prevTx.doc?._id !== undefined) {
|
||||
result.txDocIds?.add(prevTx.doc._id)
|
||||
}
|
||||
if (result.doc?._id !== undefined) {
|
||||
result.txDocIds?.add(result.doc._id)
|
||||
}
|
||||
result.txes.push(...prevTx.txes, prevTx)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
newResults.push(result)
|
||||
return newResults
|
||||
}
|
||||
|
||||
isInitTx (prevTx: DisplayTx, result: DisplayTx): boolean {
|
||||
if (prevTx.createTx !== undefined) {
|
||||
if (
|
||||
prevTx.tx.modifiedBy === result.tx.modifiedBy &&
|
||||
(result.tx.objectId === prevTx.createTx.objectId ||
|
||||
(result.doc as AttachedDoc)?.attachedTo === prevTx.createTx.objectId)
|
||||
) {
|
||||
return result.tx.modifiedOn - prevTx.createTx.modifiedOn < createCombineThreshold
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
isSameKindTx (prevTx: DisplayTx, result: DisplayTx): boolean {
|
||||
return (
|
||||
prevTx.tx.objectId === result.tx.objectId && // Same document id
|
||||
prevTx.tx._class === result.tx._class && // Same transaction class
|
||||
prevTx.tx.modifiedBy === result.tx.modifiedBy // Same user
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
function getCombineOpFromTx (result: DisplayTx): any {
|
||||
let curUpdate: any
|
||||
if (result.tx._class === core.class.TxUpdateDoc) {
|
||||
curUpdate = (result.tx as unknown as TxUpdateDoc<Doc>).operations
|
||||
}
|
||||
if (result.tx._class === core.class.TxMixin) {
|
||||
curUpdate = (result.tx as unknown as TxMixin<Doc, Doc>).attributes
|
||||
}
|
||||
if (curUpdate === undefined && result.collectionAttribute !== undefined) {
|
||||
curUpdate = result.collectionAttribute.attributeOf + '.' + result.collectionAttribute.name
|
||||
}
|
||||
return curUpdate
|
||||
}
|
||||
|
||||
export function newDisplayTx (
|
||||
tx: TxCUD<Doc>,
|
||||
hierarchy: Hierarchy,
|
||||
@@ -462,11 +45,3 @@ export function newDisplayTx (
|
||||
originTx
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct an new activity, to listend for displayed transactions in UI.
|
||||
* @param client
|
||||
*/
|
||||
export function newActivity (client: Client, attributes: Map<string, AnyAttribute>): Activity {
|
||||
return new ActivityImpl(client, attributes)
|
||||
}
|
||||
|
||||
@@ -13,161 +13,83 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import activity, { ActivityExtension, DisplayTx, TxViewlet } from '@hcengineering/activity'
|
||||
import core, { Class, Doc, Ref, SortingOrder } from '@hcengineering/core'
|
||||
import notification, { DocUpdates, DocUpdateTx, Writable } from '@hcengineering/notification'
|
||||
import activity from '@hcengineering/activity'
|
||||
import { Doc, Ref, SortingOrder } from '@hcengineering/core'
|
||||
import notification, { DisplayActivityMessage, ActivityMessage } from '@hcengineering/notification'
|
||||
import { getResource } from '@hcengineering/platform'
|
||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||
import { Grid, Label, Lazy, Spinner } from '@hcengineering/ui'
|
||||
import { ActivityKey, activityKey, newActivity } from '../activity'
|
||||
import { filterCollectionTxes, getExtensions } from '../utils'
|
||||
import { createQuery } from '@hcengineering/presentation'
|
||||
import { Component, Grid, Label, Lazy, Spinner } from '@hcengineering/ui'
|
||||
|
||||
import ActivityFilter from './ActivityFilter.svelte'
|
||||
import TxView from './TxView.svelte'
|
||||
import ActivityExtensionComponent from './ActivityExtensionComponent.svelte'
|
||||
|
||||
export let object: Doc
|
||||
export let showCommenInput: boolean = true
|
||||
export let transparent: boolean = false
|
||||
export let shouldScroll: boolean = false
|
||||
export let focusIndex: number = -1
|
||||
export let boundary: HTMLElement | undefined = undefined
|
||||
|
||||
let extensions: ActivityExtension[] = []
|
||||
const activityMessagesQuery = createQuery()
|
||||
|
||||
$: if (object) {
|
||||
getExtensions(client, object._class).then((res?: ActivityExtension[]) => {
|
||||
extensions = res || []
|
||||
})
|
||||
}
|
||||
let filteredMessages: DisplayActivityMessage[] = []
|
||||
let activityMessages: ActivityMessage[] = []
|
||||
let isLoading = false
|
||||
|
||||
getResource(notification.function.GetNotificationClient).then((res) => {
|
||||
updatesStore = res().docUpdatesStore
|
||||
})
|
||||
let updatesStore: Writable<Map<Ref<Doc>, DocUpdates>> | undefined
|
||||
let isNewestFirst = JSON.parse(localStorage.getItem('activity-newest-first') ?? 'false')
|
||||
|
||||
$: updates = $updatesStore?.get(object._id)
|
||||
$: newTxes = updates?.txes ?? []
|
||||
async function updateActivityMessages (objectId: Ref<Doc>, order: SortingOrder): Promise<void> {
|
||||
isLoading = true
|
||||
const combineMessagesFn = await getResource(notification.function.CombineActivityMessages)
|
||||
|
||||
let txes: DisplayTx[] = []
|
||||
|
||||
const client = getClient()
|
||||
const attrs = client.getHierarchy().getAllAttributes(object._class)
|
||||
|
||||
const activityQuery = newActivity(client, attrs)
|
||||
|
||||
let viewlets = new Map<ActivityKey, TxViewlet[]>()
|
||||
|
||||
let allViewlets: TxViewlet[] = []
|
||||
let editableMap: Map<Ref<Class<Doc>>, boolean> | undefined = undefined
|
||||
|
||||
const descriptors = createQuery()
|
||||
$: descriptors.query(activity.class.TxViewlet, {}, (result) => {
|
||||
allViewlets = result
|
||||
editableMap = new Map(
|
||||
allViewlets
|
||||
.filter((tx) => tx.txClass === core.class.TxCreateDoc)
|
||||
.map((it) => [it.objectClass, it.editable ?? false])
|
||||
)
|
||||
})
|
||||
|
||||
$: viewlets = buildViewletsMap(allViewlets)
|
||||
|
||||
function buildViewletsMap (allViewlets: TxViewlet[]): Map<ActivityKey, TxViewlet[]> {
|
||||
const viewlets = new Map()
|
||||
for (const res of allViewlets) {
|
||||
const key = activityKey(res.objectClass, res.txClass)
|
||||
const arr = viewlets.get(key) ?? []
|
||||
arr.push(res)
|
||||
viewlets.set(key, arr)
|
||||
}
|
||||
return viewlets
|
||||
}
|
||||
|
||||
let loading = false
|
||||
let activityOrderNewestFirst = JSON.parse(localStorage.getItem('activity-newest-first') ?? 'false')
|
||||
function updateTxes (
|
||||
objectId: Ref<Doc>,
|
||||
objectClass: Ref<Class<Doc>>,
|
||||
editableMap: Map<Ref<Class<Doc>>, boolean> | undefined,
|
||||
activityOrder: boolean
|
||||
): void {
|
||||
loading = true
|
||||
const res = activityQuery.update(
|
||||
objectId,
|
||||
objectClass,
|
||||
(_id, result) => {
|
||||
if (_id === objectId) {
|
||||
txes = filterCollectionTxes(result)
|
||||
|
||||
if (txes.length > 0) {
|
||||
loading = false
|
||||
}
|
||||
}
|
||||
const res = activityMessagesQuery.query(
|
||||
notification.class.ActivityMessage,
|
||||
{ attachedTo: objectId },
|
||||
(result: ActivityMessage[]) => {
|
||||
activityMessages = combineMessagesFn(result, order)
|
||||
isLoading = false
|
||||
},
|
||||
activityOrder ? SortingOrder.Descending : SortingOrder.Ascending,
|
||||
editableMap ?? new Map()
|
||||
{
|
||||
sort: {
|
||||
createdOn: SortingOrder.Ascending
|
||||
}
|
||||
}
|
||||
)
|
||||
if (!res) {
|
||||
loading = false
|
||||
isLoading = false
|
||||
}
|
||||
}
|
||||
|
||||
$: updateTxes(object._id, object._class, editableMap, activityOrderNewestFirst)
|
||||
|
||||
let filtered: DisplayTx[] = []
|
||||
|
||||
let newTxIndexes: number[] = []
|
||||
$: newTxIndexes = getNewTxes(filtered, newTxes)
|
||||
|
||||
function getNewTxes (filtered: DisplayTx[], newTxes: DocUpdateTx[]): number[] {
|
||||
const res: number[] = []
|
||||
for (let i = 0; i < filtered.length; i++) {
|
||||
if (isNew(filtered[i], newTxes)) {
|
||||
res.push(i)
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
function isNew (tx: DisplayTx | undefined, newTxes: DocUpdateTx[]): boolean {
|
||||
if (tx === undefined) return false
|
||||
const index = newTxes.findIndex((p) => p._id === tx.originTx._id && p.isNew)
|
||||
return index !== -1
|
||||
}
|
||||
|
||||
$: scrollIndex = shouldScroll ? newTxIndexes[0] ?? filtered.length - 1 : -1
|
||||
$: updateActivityMessages(object._id, isNewestFirst ? SortingOrder.Descending : SortingOrder.Ascending)
|
||||
</script>
|
||||
|
||||
<div class="antiSection-header high mt-9" class:invisible={transparent}>
|
||||
<span class="antiSection-header__title flex-row-center">
|
||||
<Label label={activity.string.Activity} />
|
||||
{#if loading}
|
||||
{#if isLoading}
|
||||
<div class="ml-1">
|
||||
<Spinner size={'small'} />
|
||||
<Spinner size="small" />
|
||||
</div>
|
||||
{/if}
|
||||
</span>
|
||||
<ActivityFilter
|
||||
{txes}
|
||||
messages={activityMessages}
|
||||
{object}
|
||||
on:update={(e) => {
|
||||
filtered = e.detail
|
||||
filteredMessages = e.detail
|
||||
}}
|
||||
bind:activityOrderNewestFirst
|
||||
bind:isNewestFirst
|
||||
/>
|
||||
</div>
|
||||
<div class="p-activity select-text" id={activity.string.Activity}>
|
||||
{#if filtered}
|
||||
<Grid column={1} rowGap={0.75}>
|
||||
{#each filtered as tx, i}
|
||||
{#if filteredMessages.length}
|
||||
<Grid column={1} rowGap={0}>
|
||||
{#each filteredMessages as message}
|
||||
<Lazy>
|
||||
<TxView
|
||||
{tx}
|
||||
{viewlets}
|
||||
isNew={newTxIndexes.includes(i)}
|
||||
isNextNew={newTxIndexes.includes(i + 1)}
|
||||
shouldScroll={i === scrollIndex}
|
||||
{boundary}
|
||||
<Component
|
||||
is={notification.component.ActivityMessagePresenter}
|
||||
props={{
|
||||
value: message,
|
||||
boundary
|
||||
}}
|
||||
/>
|
||||
</Lazy>
|
||||
{/each}
|
||||
@@ -176,7 +98,7 @@
|
||||
</div>
|
||||
{#if showCommenInput}
|
||||
<div class="ref-input">
|
||||
<ActivityExtensionComponent {extensions} kind="input" props={{ object, focusIndex, boundary }} />
|
||||
<Component is={notification.component.ChatMessageInput} props={{ object, boundary, focusIndex }} />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -186,14 +108,18 @@
|
||||
margin-top: 1.75rem;
|
||||
padding-bottom: 2.5rem;
|
||||
}
|
||||
|
||||
.p-activity {
|
||||
margin-top: 1.75rem;
|
||||
}
|
||||
|
||||
.invisible {
|
||||
display: none;
|
||||
}
|
||||
|
||||
:global(.grid .msgactivity-container.showIcon:last-child::after) {
|
||||
content: none;
|
||||
} // Remove the line in the last Activity message
|
||||
}
|
||||
|
||||
// Remove the line in the last Activity message
|
||||
</style>
|
||||
|
||||
@@ -13,32 +13,36 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import activity, { ActivityFilter, DisplayTx } from '@hcengineering/activity'
|
||||
import { Class, Doc, Ref } from '@hcengineering/core'
|
||||
import { getResource } from '@hcengineering/platform'
|
||||
import { getClient, hasResource } from '@hcengineering/presentation'
|
||||
import { ActionIcon, AnyComponent, Icon, Label, eventToHTMLElement, showPopup } from '@hcengineering/ui'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import activityPlg from '../plugin'
|
||||
import { Doc, Ref, SortingOrder } from '@hcengineering/core'
|
||||
import { getResource } from '@hcengineering/platform'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import { ActionIcon, eventToHTMLElement, Icon, Label, showPopup } from '@hcengineering/ui'
|
||||
import notification, { ActivityMessage, ActivityMessagesFilter } from '@hcengineering/notification'
|
||||
|
||||
import activity from '../plugin'
|
||||
import FilterPopup from './FilterPopup.svelte'
|
||||
import IconClose from './icons/Close.svelte'
|
||||
import IconFilter from './icons/Filter.svelte'
|
||||
|
||||
export let txes: DisplayTx[]
|
||||
export let messages: ActivityMessage[]
|
||||
export let object: Doc
|
||||
export let isNewestFirst = false
|
||||
|
||||
let filtered: DisplayTx[]
|
||||
let filtered: ActivityMessage[]
|
||||
const client = getClient()
|
||||
let filters: ActivityFilter[] = []
|
||||
let filters: ActivityMessagesFilter[] = []
|
||||
const saved = localStorage.getItem('activity-filter')
|
||||
export let activityOrderNewestFirst = false
|
||||
let selectedFiltersRefs: Ref<Doc>[] | 'All' =
|
||||
saved !== null && saved !== undefined ? (JSON.parse(saved) as Ref<Doc>[] | 'All') : 'All'
|
||||
let selectedFilters: ActivityFilter[] = []
|
||||
let selectedFilters: ActivityMessagesFilter[] = []
|
||||
|
||||
$: localStorage.setItem('activity-filter', JSON.stringify(selectedFiltersRefs))
|
||||
$: localStorage.setItem('activity-newest-first', JSON.stringify(activityOrderNewestFirst))
|
||||
client.findAll(activity.class.ActivityFilter, {}).then((res) => {
|
||||
$: localStorage.setItem('activity-newest-first', JSON.stringify(isNewestFirst))
|
||||
|
||||
client.findAll(notification.class.ActivityMessagesFilter, {}).then((res) => {
|
||||
filters = res
|
||||
|
||||
if (saved !== null && saved !== undefined) {
|
||||
const temp: Ref<Doc>[] | 'All' = JSON.parse(saved)
|
||||
if (temp !== 'All' && Array.isArray(temp)) {
|
||||
@@ -52,20 +56,7 @@
|
||||
}
|
||||
})
|
||||
|
||||
function getAdditionalComponent (_class: Ref<Class<Doc>>): AnyComponent | undefined {
|
||||
const hierarchy = client.getHierarchy()
|
||||
const mixin = hierarchy.classHierarchyMixin(_class, activity.mixin.ExtraActivityComponent, (m) =>
|
||||
hasResource(m.component)
|
||||
)
|
||||
if (mixin !== undefined) {
|
||||
return mixin.component
|
||||
}
|
||||
}
|
||||
|
||||
let extraComponent = getAdditionalComponent(object._class)
|
||||
$: extraComponent = getAdditionalComponent(object._class)
|
||||
|
||||
const handleOptions = (ev: MouseEvent) => {
|
||||
const handleOptions = (ev: MouseEvent): void => {
|
||||
showPopup(
|
||||
FilterPopup,
|
||||
{ selectedFiltersRefs, filters },
|
||||
@@ -74,7 +65,7 @@
|
||||
(res) => {
|
||||
if (res === undefined) return
|
||||
if (res.action === 'toggle') {
|
||||
activityOrderNewestFirst = res.value
|
||||
isNewestFirst = res.value
|
||||
return
|
||||
}
|
||||
const selected = res.value as Ref<Doc>[]
|
||||
@@ -87,33 +78,45 @@
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
async function updateFilterActions (
|
||||
txes: DisplayTx[],
|
||||
filters: ActivityFilter[],
|
||||
selected: Ref<Doc>[] | 'All'
|
||||
messages: ActivityMessage[],
|
||||
filters: ActivityMessagesFilter[],
|
||||
selected: Ref<Doc>[] | 'All',
|
||||
sortOrder: SortingOrder
|
||||
): Promise<void> {
|
||||
const txesSorted = txes.sort((tx) => ((tx.doc as any)?.pinned ? -1 : 1))
|
||||
const sortMessagesFn = await getResource(notification.function.SortActivityMessages)
|
||||
|
||||
const sortedMessages = sortMessagesFn(messages, sortOrder).sort(({ isPinned }) =>
|
||||
isPinned && sortOrder === SortingOrder.Ascending ? -1 : 1
|
||||
)
|
||||
|
||||
if (selected === 'All') {
|
||||
filtered = txesSorted
|
||||
filtered = sortedMessages
|
||||
|
||||
dispatch('update', filtered)
|
||||
} else {
|
||||
selectedFilters = filters.filter((filter) => selected.includes(filter._id))
|
||||
const filterActions: ((tx: DisplayTx, _class?: Ref<Doc>) => boolean)[] = []
|
||||
const filterActions: ((message: ActivityMessage, _class?: Ref<Doc>) => boolean)[] = []
|
||||
for (const filter of selectedFilters) {
|
||||
const fltr = await getResource(filter.filter)
|
||||
filterActions.push(fltr)
|
||||
}
|
||||
filtered = txesSorted.filter((it) => filterActions.some((f) => f(it, object._class)))
|
||||
filtered = messages.filter((message) => filterActions.some((f) => f(message, object._class)))
|
||||
dispatch('update', filtered)
|
||||
}
|
||||
}
|
||||
|
||||
$: updateFilterActions(txes, filters, selectedFiltersRefs)
|
||||
$: updateFilterActions(
|
||||
messages,
|
||||
filters,
|
||||
selectedFiltersRefs,
|
||||
isNewestFirst ? SortingOrder.Descending : SortingOrder.Ascending
|
||||
)
|
||||
</script>
|
||||
|
||||
<div class="w-4 min-w-4 max-w-4" />
|
||||
{#if selectedFiltersRefs === 'All'}
|
||||
<div class="antiSection-header__tag highlight">
|
||||
<Label label={activityPlg.string.All} />
|
||||
<Label label={activity.string.All} />
|
||||
</div>
|
||||
{:else}
|
||||
{#each selectedFilters as filter}
|
||||
@@ -137,10 +140,3 @@
|
||||
{/if}
|
||||
<div class="w-4 min-w-4 max-w-4" />
|
||||
<ActionIcon icon={IconFilter} size={'medium'} action={handleOptions} />
|
||||
{#if extraComponent}
|
||||
{#await getResource(extraComponent) then comp}
|
||||
{#if comp}
|
||||
<svelte:component this={comp} {filtered} {object} on:update />
|
||||
{/if}
|
||||
{/await}
|
||||
{/if}
|
||||
|
||||
@@ -17,14 +17,17 @@
|
||||
import { IntlString } from '@hcengineering/platform'
|
||||
import { Label, resizeObserver, CheckBox, MiniToggle } from '@hcengineering/ui'
|
||||
import { Doc, Ref } from '@hcengineering/core'
|
||||
import { ActivityFilter } from '@hcengineering/activity'
|
||||
import notification, { ActivityMessagesFilter } from '@hcengineering/notification'
|
||||
|
||||
import activity from '../plugin'
|
||||
|
||||
export let selectedFiltersRefs: Ref<Doc>[] | 'All' = 'All'
|
||||
export let filters: ActivityFilter[] = []
|
||||
let activityOrderNewestFirst = JSON.parse(localStorage.getItem('activity-newest-first') ?? 'false')
|
||||
export let filters: ActivityMessagesFilter[] = []
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
let activityOrderNewestFirst = JSON.parse(localStorage.getItem('activity-newest-first') ?? 'false')
|
||||
|
||||
interface ActionMenu {
|
||||
label: IntlString
|
||||
checked: boolean
|
||||
@@ -129,7 +132,7 @@
|
||||
<div class="ml-3 mt-2 mb-2 mr-3">
|
||||
<MiniToggle
|
||||
bind:on={activityOrderNewestFirst}
|
||||
label={activity.string.NewestFirst}
|
||||
label={notification.string.NewestFirst}
|
||||
on:change={() => {
|
||||
dispatch('update', { action: 'toggle', value: activityOrderNewestFirst })
|
||||
}}
|
||||
|
||||
@@ -1,550 +0,0 @@
|
||||
<!--
|
||||
// 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 { tick } from 'svelte'
|
||||
import type { ActivityExtension, DisplayTx, TxViewlet } from '@hcengineering/activity'
|
||||
import attachment from '@hcengineering/attachment'
|
||||
import contact, { Person, PersonAccount, getName } from '@hcengineering/contact'
|
||||
import core, { AnyAttribute, Doc, Ref, TxCUD, getCurrentAccount } from '@hcengineering/core'
|
||||
import { Asset } from '@hcengineering/platform'
|
||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||
import ui, {
|
||||
ActionIcon,
|
||||
AnyComponent,
|
||||
Component,
|
||||
Icon,
|
||||
IconActivityEdit,
|
||||
IconEdit,
|
||||
IconMoreH,
|
||||
Label,
|
||||
ShowMore,
|
||||
TimeSince,
|
||||
showPopup
|
||||
} from '@hcengineering/ui'
|
||||
import type { AttributeModel } from '@hcengineering/view'
|
||||
import { Menu, ObjectPresenter } from '@hcengineering/view-resources'
|
||||
|
||||
import TxViewTx from './TxViewTx.svelte'
|
||||
import ActivityExtensionComponent from './ActivityExtensionComponent.svelte'
|
||||
|
||||
import { ActivityKey } from '../activity'
|
||||
import activity from '../plugin'
|
||||
import { TxDisplayViewlet, getPrevValue, getValue, updateViewlet, getExtensions } from '../utils'
|
||||
|
||||
export let tx: DisplayTx
|
||||
export let viewlets: Map<ActivityKey, TxViewlet[]>
|
||||
export let showIcon: boolean = true
|
||||
export let isNew: boolean = false
|
||||
export let isNextNew: boolean = false
|
||||
export let contentHidden: boolean = false
|
||||
export let shouldScroll: boolean = false
|
||||
export let boundary: HTMLElement | undefined = undefined
|
||||
// export let showDocument = false
|
||||
|
||||
let ptx: DisplayTx | undefined
|
||||
|
||||
let viewlet: TxDisplayViewlet | undefined
|
||||
let props: any
|
||||
let account: PersonAccount | undefined
|
||||
let person: Person | undefined
|
||||
let model: AttributeModel[] = []
|
||||
let modelIcon: Asset | undefined = undefined
|
||||
let iconComponent: AnyComponent | undefined = undefined
|
||||
let extensions: ActivityExtension[] = []
|
||||
|
||||
let edit: boolean = false
|
||||
let showDiff: boolean = false
|
||||
|
||||
const currentAccount = getCurrentAccount() as PersonAccount
|
||||
|
||||
$: if (tx.doc?._class) {
|
||||
getExtensions(client, tx.doc._class).then((res?: ActivityExtension[]) => {
|
||||
extensions = res || []
|
||||
})
|
||||
}
|
||||
|
||||
$: if (tx.tx._id !== ptx?.tx._id) {
|
||||
if (tx.tx.modifiedBy !== account?._id) {
|
||||
account = undefined
|
||||
person = undefined
|
||||
}
|
||||
viewlet = undefined
|
||||
props = undefined
|
||||
model = []
|
||||
ptx = tx
|
||||
}
|
||||
|
||||
const client = getClient()
|
||||
const query = createQuery()
|
||||
const employeeQuery = createQuery()
|
||||
|
||||
function getProps (props: any, edit: boolean): any {
|
||||
return { ...props, edit, attr: tx.collectionAttribute, boundary }
|
||||
}
|
||||
|
||||
$: updateViewlet(client, viewlets, tx).then((result) => {
|
||||
if (result.id === tx.tx._id) {
|
||||
viewlet = result.viewlet
|
||||
model = result.model
|
||||
modelIcon = result.modelIcon
|
||||
iconComponent = result.iconComponent
|
||||
props = getProps(result.props, edit)
|
||||
if (shouldScroll) {
|
||||
tick().then(scrollIntoView)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
$: query.query(
|
||||
contact.class.PersonAccount,
|
||||
{ _id: tx.tx.modifiedBy as Ref<PersonAccount> },
|
||||
(res) => {
|
||||
;[account] = res
|
||||
},
|
||||
{ limit: 1 }
|
||||
)
|
||||
|
||||
$: account &&
|
||||
employeeQuery.query(
|
||||
contact.class.Person,
|
||||
{ _id: account.person },
|
||||
(res) => {
|
||||
;[person] = res
|
||||
},
|
||||
{ limit: 1 }
|
||||
)
|
||||
|
||||
const showMenu = async (ev: MouseEvent): Promise<void> => {
|
||||
showPopup(
|
||||
Menu,
|
||||
{
|
||||
object: tx.doc as Doc,
|
||||
actions: [
|
||||
{
|
||||
label: activity.string.Edit,
|
||||
icon: IconEdit,
|
||||
action: () => {
|
||||
edit = true
|
||||
props = getProps(props, edit)
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
ev.target as HTMLElement
|
||||
)
|
||||
}
|
||||
const onCancelEdit = () => {
|
||||
edit = false
|
||||
props = getProps(props, edit)
|
||||
}
|
||||
|
||||
function isMessageType (attr?: AnyAttribute): boolean {
|
||||
return attr?.type._class === core.class.TypeMarkup
|
||||
}
|
||||
|
||||
function isAttachment (tx: TxCUD<Doc>): boolean {
|
||||
return tx.objectClass === attachment.class.Attachment && tx._class === core.class.TxCreateDoc
|
||||
}
|
||||
|
||||
function isMention (extensions: ActivityExtension[]): boolean {
|
||||
return extensions.some(({ isMention }) => !!isMention)
|
||||
}
|
||||
|
||||
async function updateMessageType (model: AttributeModel[], tx: DisplayTx): Promise<boolean> {
|
||||
for (const m of model) {
|
||||
if (isMessageType(m.attribute)) {
|
||||
return true
|
||||
}
|
||||
const val = await getValue(client, m, tx)
|
||||
if (val.added.length > 1 || val.removed.length > 1) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
let hasMessageType = false
|
||||
$: updateMessageType(model, tx).then((res) => {
|
||||
hasMessageType = res
|
||||
})
|
||||
$: isComment = viewlet && viewlet?.editable
|
||||
$: isAttached = isAttachment(tx.tx)
|
||||
$: isMentioned = isMention(extensions)
|
||||
$: withAvatar = isComment || isMentioned || isAttached
|
||||
$: isEmphasized = viewlet?.display === 'emphasized' || model.every((m) => isMessageType(m.attribute))
|
||||
$: isColumn = isComment || isEmphasized || hasMessageType
|
||||
|
||||
let htmlElement: HTMLDivElement
|
||||
|
||||
function scrollIntoView () {
|
||||
htmlElement?.scrollIntoView({ behavior: 'auto', block: 'start' })
|
||||
shouldScroll = false
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if (viewlet !== undefined && !((viewlet?.hideOnRemove ?? false) && tx.removed)) || model.length > 0}
|
||||
<div
|
||||
class="msgactivity-container"
|
||||
bind:this={htmlElement}
|
||||
class:showIcon
|
||||
class:withAvatar
|
||||
class:isNew
|
||||
class:isNextNew
|
||||
>
|
||||
{#if showIcon}
|
||||
{#if withAvatar}
|
||||
<div class="msgactivity-avatar">
|
||||
<Component
|
||||
is={contact.component.Avatar}
|
||||
props={{ avatar: person?.avatar, size: 'medium', name: person?.name }}
|
||||
/>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="msgactivity-icon">
|
||||
{#if iconComponent}
|
||||
<Component is={iconComponent} {props} />
|
||||
{:else if viewlet}
|
||||
<Icon icon={viewlet.icon} size="small" />
|
||||
{:else if viewlet === undefined && model.length > 0}
|
||||
<Icon icon={modelIcon !== undefined ? modelIcon : IconActivityEdit} size="small" />
|
||||
{:else}
|
||||
<Icon icon={IconActivityEdit} size="small" />
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
<div class="msgactivity-content" class:content={isColumn} class:comment={withAvatar}>
|
||||
<div class="msgactivity-content__header">
|
||||
<div class="msgactivity-content__title labels-row">
|
||||
<span class={withAvatar ? 'bold' : 'strong'}>
|
||||
{#if person}
|
||||
{getName(client.getHierarchy(), person)}
|
||||
{:else}
|
||||
<Label label={core.string.System} />
|
||||
{/if}
|
||||
</span>
|
||||
|
||||
{#if viewlet && viewlet?.editable}
|
||||
{#if viewlet.label}
|
||||
<span class="lower"><Label label={viewlet.label} params={viewlet.labelParams ?? {}} /></span>
|
||||
{/if}
|
||||
{#if tx.updated}
|
||||
<span class="lower"><Label label={activity.string.Edited} /></span>
|
||||
{/if}
|
||||
<span class="time ml-1"><TimeSince value={tx.tx.modifiedOn} /></span>
|
||||
{:else if viewlet && viewlet.label}
|
||||
<span class="lower whitespace-nowrap">
|
||||
<Label label={viewlet.label} params={viewlet.labelParams ?? {}} />
|
||||
</span>
|
||||
{/if}
|
||||
{#if viewlet && viewlet.labelComponent}
|
||||
<Component is={viewlet.labelComponent} {props} />
|
||||
{/if}
|
||||
|
||||
{#if viewlet === undefined && model.length > 0 && tx.updateTx}
|
||||
{#each model as m}
|
||||
{#await getValue(client, m, tx) then value}
|
||||
{#if value.added.length}
|
||||
<span class="lower"><Label label={activity.string.Added} /></span>
|
||||
<span class="lower"><Label label={activity.string.To} /></span>
|
||||
<span class="lower"><Label label={m.label} /></span>
|
||||
{#each value.added as cvalue}
|
||||
{#if value.isObjectAdded}
|
||||
<ObjectPresenter value={cvalue} accent />
|
||||
{:else}
|
||||
<svelte:component this={m.presenter} value={cvalue} accent />
|
||||
{/if}
|
||||
{/each}
|
||||
{:else if value.removed.length}
|
||||
<span class="lower"><Label label={activity.string.Removed} /></span>
|
||||
<span class="lower"><Label label={activity.string.From} /></span>
|
||||
<span class="lower"><Label label={m.label} /></span>
|
||||
{#each value.removed as cvalue}
|
||||
{#if value.isObjectRemoved}
|
||||
<ObjectPresenter value={cvalue} accent />
|
||||
{:else}
|
||||
<svelte:component this={m.presenter} value={cvalue} accent />
|
||||
{/if}
|
||||
{/each}
|
||||
{:else if value.set === null || value.set === undefined || value.set === ''}
|
||||
<span class="lower"><Label label={activity.string.Unset} /></span>
|
||||
<span class="lower"><Label label={m.label} /></span>
|
||||
{:else}
|
||||
<span class="lower"><Label label={activity.string.Changed} /></span>
|
||||
<span class="lower"><Label label={m.label} /></span>
|
||||
|
||||
{#if !hasMessageType}
|
||||
<span class="lower"><Label label={activity.string.To} /></span>
|
||||
<span class="strong overflow-label">
|
||||
{#if value.isObjectSet}
|
||||
<ObjectPresenter value={value.set} accent />
|
||||
{:else}
|
||||
<svelte:component this={m.presenter} value={value.set} accent />
|
||||
{/if}
|
||||
</span>
|
||||
{:else}
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<span class="show-diff" on:click={() => (showDiff = !showDiff)}>
|
||||
<Label label={showDiff ? ui.string.ShowLess : ui.string.ShowMore} />
|
||||
</span>
|
||||
{/if}
|
||||
{/if}
|
||||
{/await}
|
||||
{/each}
|
||||
{:else if viewlet === undefined && model.length > 0 && tx.mixinTx}
|
||||
{#each model as m}
|
||||
{#await getValue(client, m, tx) then value}
|
||||
{#if value.set === null || value.set === ''}
|
||||
<span class="lower"><Label label={activity.string.Unset} /></span>
|
||||
<span class="lower"><Label label={m.label} /></span>
|
||||
{:else}
|
||||
<span class="lower"><Label label={activity.string.Changed} /></span>
|
||||
<span class="lower"><Label label={m.label} /></span>
|
||||
<span class="lower"><Label label={activity.string.To} /></span>
|
||||
|
||||
{#if !hasMessageType}
|
||||
<div class="strong overflow-label">
|
||||
{#if value.isObjectSet}
|
||||
<ObjectPresenter value={value.set} accent />
|
||||
{:else}
|
||||
<svelte:component this={m.presenter} value={value.set} accent />
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
{/await}
|
||||
{/each}
|
||||
{:else if viewlet && viewlet.display === 'inline' && viewlet.component}
|
||||
{#if tx.collectionAttribute !== undefined && (tx.txDocIds?.size ?? 0) > 1}
|
||||
<TxViewTx {tx} {onCancelEdit} {edit} {viewlet} />
|
||||
{:else if typeof viewlet.component === 'string'}
|
||||
<Component is={viewlet.component} {props} on:close={onCancelEdit} />
|
||||
{:else}
|
||||
<svelte:component this={viewlet.component} {...props} on:close={onCancelEdit} />
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
{#if isComment}
|
||||
<div class="buttons-group">
|
||||
<ActivityExtensionComponent {extensions} kind="action" props={{ object: tx.doc }} />
|
||||
<!-- <Like /> -->
|
||||
{#if account?.person === currentAccount?.person}
|
||||
<ActionIcon icon={IconMoreH} size={'small'} action={showMenu} />
|
||||
{/if}
|
||||
</div>
|
||||
{:else if hasMessageType || isColumn}
|
||||
<span class="time top ml-4"><TimeSince value={tx.tx.modifiedOn} /></span>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if !isColumn}
|
||||
<span class="time top ml-4"><TimeSince value={tx.tx.modifiedOn} /></span>
|
||||
{/if}
|
||||
|
||||
{#if viewlet && viewlet.display !== 'inline'}
|
||||
<div class="activity-content content" class:indent={isAttached} class:contentHidden>
|
||||
<ShowMore ignore={edit}>
|
||||
{#if tx.collectionAttribute !== undefined && (tx.txDocIds?.size ?? 0) > 1}
|
||||
<div class="flex-row-center flex-grow flex-wrap m--1 gap-around-2 clear-mins">
|
||||
<TxViewTx {tx} {onCancelEdit} {edit} {viewlet} presentersOnly={isAttached} />
|
||||
</div>
|
||||
{:else if typeof viewlet.component === 'string'}
|
||||
<Component is={viewlet.component} {props} on:close={onCancelEdit} />
|
||||
{:else}
|
||||
<svelte:component this={viewlet.component} {...props} on:close={onCancelEdit} />
|
||||
{/if}
|
||||
</ShowMore>
|
||||
</div>
|
||||
{:else if hasMessageType && model.length > 0 && (tx.updateTx || tx.mixinTx)}
|
||||
{#await getValue(client, model[0], tx) then value}
|
||||
{@const prevValue = getPrevValue(client, model[0], tx)}
|
||||
<div class="activity-content content" class:indent={isAttached} class:contentHidden>
|
||||
<ShowMore ignore={edit || prevValue !== undefined}>
|
||||
{#if value.isObjectSet}
|
||||
<ObjectPresenter value={value.set} />
|
||||
{:else if showDiff}
|
||||
<svelte:component this={model[0].presenter} value={value.set} {prevValue} showOnlyDiff />
|
||||
{/if}
|
||||
</ShowMore>
|
||||
</div>
|
||||
{/await}
|
||||
{/if}
|
||||
<ActivityExtensionComponent {extensions} kind="footer" props={{ object: tx.doc }} />
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
.msgactivity-container {
|
||||
position: relative;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
min-width: 0;
|
||||
|
||||
.msgactivity-icon,
|
||||
.msgactivity-avatar {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-right: 1rem;
|
||||
width: 2.25rem;
|
||||
min-width: 2.25rem;
|
||||
color: var(--theme-darker-color);
|
||||
}
|
||||
|
||||
.msgactivity-icon {
|
||||
height: 1.75rem;
|
||||
}
|
||||
|
||||
.msgactivity-avatar {
|
||||
height: 2.25rem;
|
||||
// background-color: var(--theme-darker-color);
|
||||
border: 1px dashed var(--divider-trans-color);
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.msgactivity-content {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
margin-right: 1rem;
|
||||
min-width: 0;
|
||||
color: var(--theme-dark-color);
|
||||
|
||||
.msgactivity-content__header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
flex-grow: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.msgactivity-content__title {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
flex-grow: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
&.content {
|
||||
flex-direction: column;
|
||||
padding-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
&:not(.comment) {
|
||||
.msgactivity-content__header {
|
||||
min-height: 1.75rem;
|
||||
}
|
||||
}
|
||||
|
||||
&:not(.content) {
|
||||
align-items: center;
|
||||
|
||||
.msgactivity-content__header {
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.showIcon {
|
||||
&::after,
|
||||
&::before {
|
||||
position: absolute;
|
||||
left: 1.125rem;
|
||||
width: 1px;
|
||||
background-color: var(--divider-trans-color);
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
&.isNew::before {
|
||||
background-color: var(--highlight-red);
|
||||
}
|
||||
|
||||
&.isNextNew {
|
||||
&::after {
|
||||
background-color: var(--highlight-red);
|
||||
}
|
||||
}
|
||||
|
||||
&::before {
|
||||
top: -0.75rem;
|
||||
height: 0.75rem;
|
||||
}
|
||||
|
||||
&.withAvatar::after {
|
||||
content: '';
|
||||
top: 2.25rem;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
&:not(.withAvatar)::after {
|
||||
content: '';
|
||||
top: 1.75rem;
|
||||
bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
:global(.msgactivity-container + .msgactivity-container::before) {
|
||||
content: '';
|
||||
}
|
||||
|
||||
.time {
|
||||
font-size: 0.75rem;
|
||||
color: var(--theme-trans-color);
|
||||
|
||||
&.top {
|
||||
align-self: flex-start;
|
||||
}
|
||||
}
|
||||
|
||||
.activity-content {
|
||||
visibility: visible;
|
||||
min-width: 0;
|
||||
max-height: max-content;
|
||||
opacity: 1;
|
||||
position: relative;
|
||||
transition-property: max-height, opacity;
|
||||
transition-timing-function: ease-in-out;
|
||||
transition-duration: 0.15s;
|
||||
|
||||
&.contentHidden {
|
||||
visibility: hidden;
|
||||
padding: 0;
|
||||
margin-top: -0.5rem;
|
||||
max-height: 0;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
&.indent {
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.show-diff {
|
||||
color: var(--theme-content-color);
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
color: var(--theme-caption-color);
|
||||
}
|
||||
|
||||
&:active {
|
||||
color: var(--theme-content-color);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,46 +0,0 @@
|
||||
<script lang="ts">
|
||||
import core, { Class, Doc, Ref } from '@hcengineering/core'
|
||||
import { Component, IconAdd, IconDelete } from '@hcengineering/ui'
|
||||
import { getDTxProps, TxDisplayViewlet } from '../utils'
|
||||
import { DisplayTx } from '@hcengineering/activity'
|
||||
|
||||
export let tx: DisplayTx
|
||||
export let viewlet: TxDisplayViewlet
|
||||
export let edit: boolean
|
||||
export let presentersOnly: boolean = false
|
||||
export let onCancelEdit: () => void
|
||||
|
||||
function filterTx (dtx: DisplayTx[], _class: Ref<Class<Doc>>): DisplayTx[] {
|
||||
return dtx.filter((it) => it.tx._class === _class)
|
||||
}
|
||||
function getProps (ctx: DisplayTx, edit: boolean): any {
|
||||
if (viewlet?.pseudo) {
|
||||
return { value: ctx.doc }
|
||||
}
|
||||
const dprops = getDTxProps(ctx)
|
||||
return { ...dprops, edit }
|
||||
}
|
||||
</script>
|
||||
|
||||
{#each filterTx([...tx.txes, tx], core.class.TxCreateDoc) as ctx, i}
|
||||
{#if i === 0 && !presentersOnly}
|
||||
<IconAdd size={'x-small'} fill={'var(--theme-trans-color)'} />
|
||||
{/if}
|
||||
{#if typeof viewlet?.component === 'string'}
|
||||
<Component is={viewlet?.component} props={getProps(ctx, edit)} disabled on:close={onCancelEdit} />
|
||||
{:else}
|
||||
<svelte:component this={viewlet?.component} {...getProps(ctx, edit)} disabled on:close={onCancelEdit} />
|
||||
{/if}
|
||||
<div class="antiHSpacer" />
|
||||
{/each}
|
||||
{#each filterTx([...tx.txes, tx], core.class.TxRemoveDoc) as ctx, i}
|
||||
{#if i === 0 && !presentersOnly}
|
||||
<IconDelete size={'x-small'} fill={'var(--theme-trans-color)'} />
|
||||
{/if}
|
||||
{#if typeof viewlet?.component === 'string'}
|
||||
<Component is={viewlet?.component} props={getProps(ctx, edit)} disabled on:close={onCancelEdit} />
|
||||
{:else}
|
||||
<svelte:component this={viewlet?.component} {...getProps(ctx, edit)} disabled on:close={onCancelEdit} />
|
||||
{/if}
|
||||
<div class="antiHSpacer" />
|
||||
{/each}
|
||||
@@ -13,25 +13,13 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { type Doc, type Ref } from '@hcengineering/core'
|
||||
import { type Resources } from '@hcengineering/platform'
|
||||
import type { DisplayTx } from '@hcengineering/activity'
|
||||
import Activity from './components/Activity.svelte'
|
||||
import TxView from './components/TxView.svelte'
|
||||
|
||||
export { TxView }
|
||||
import Activity from './components/Activity.svelte'
|
||||
|
||||
export * from './activity'
|
||||
export * from './utils'
|
||||
|
||||
export function attributeFilter (tx: DisplayTx, _class?: Ref<Doc>): boolean {
|
||||
return tx.tx.objectClass === _class
|
||||
}
|
||||
|
||||
export default async (): Promise<Resources> => ({
|
||||
filter: {
|
||||
AttributeFilter: attributeFilter
|
||||
},
|
||||
component: {
|
||||
Activity
|
||||
}
|
||||
|
||||
@@ -19,10 +19,10 @@ import { mergeIds } from '@hcengineering/platform'
|
||||
|
||||
export default mergeIds(activityId, activity, {
|
||||
string: {
|
||||
DocCreated: '' as IntlString,
|
||||
DocAdded: '' as IntlString,
|
||||
DocDeleted: '' as IntlString,
|
||||
All: '' as IntlString,
|
||||
CollectionUpdated: '' as IntlString,
|
||||
All: '' as IntlString
|
||||
DocAdded: '' as IntlString,
|
||||
DocCreated: '' as IntlString,
|
||||
DocDeleted: '' as IntlString
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { ActivityExtension, DisplayTx, TxViewlet } from '@hcengineering/activity'
|
||||
import { get } from 'svelte/store'
|
||||
import type { DisplayTx, TxViewlet } from '@hcengineering/activity'
|
||||
import core, {
|
||||
type AttachedDoc,
|
||||
type Class,
|
||||
@@ -15,7 +16,6 @@ import core, {
|
||||
type TxOperations,
|
||||
TxProcessor,
|
||||
type TxUpdateDoc,
|
||||
getObjectValue,
|
||||
matchQuery
|
||||
} from '@hcengineering/core'
|
||||
import { type Asset, type IntlString, getResource, translate } from '@hcengineering/platform'
|
||||
@@ -23,7 +23,7 @@ import { getAttributePresenterClass } from '@hcengineering/presentation'
|
||||
import { type AnyComponent, type AnySvelteComponent, ErrorPresenter, themeStore } from '@hcengineering/ui'
|
||||
import view, { type AttributeModel, type BuildModelKey, type BuildModelOptions } from '@hcengineering/view'
|
||||
import { getObjectPresenter } from '@hcengineering/view-resources'
|
||||
import { get } from 'svelte/store'
|
||||
|
||||
import { type ActivityKey, activityKey } from './activity'
|
||||
import activity from './plugin'
|
||||
|
||||
@@ -391,42 +391,3 @@ export async function getValue (client: TxOperations, m: AttributeModel, tx: Dis
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
export function getPrevValue (client: TxOperations, m: AttributeModel, tx: DisplayTx): any {
|
||||
if (tx.txes.length > 0 && tx.txes[0].prevDoc !== undefined) {
|
||||
return getObjectValue(m.key, tx.txes[0].prevDoc)
|
||||
} else if (tx.prevDoc !== undefined) {
|
||||
return getObjectValue(m.key, tx.prevDoc)
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
export function filterCollectionTxes (txes: DisplayTx[]): DisplayTx[] {
|
||||
return txes.map(filterCollectionTx).filter(Boolean) as DisplayTx[]
|
||||
}
|
||||
|
||||
function filterCollectionTx (tx: DisplayTx): DisplayTx | undefined {
|
||||
if (tx.collectionAttribute === undefined) return tx
|
||||
const txes = tx.txes.reduceRight(
|
||||
(txes, ctx) => {
|
||||
const filtredTxes = txes.filter(
|
||||
({ tx: { _class }, doc }) => doc?._id !== ctx.doc?._id || _class === core.class.TxUpdateDoc
|
||||
)
|
||||
return ctx.tx._class === core.class.TxUpdateDoc || filtredTxes.length === txes.length
|
||||
? [ctx, ...txes]
|
||||
: filtredTxes
|
||||
},
|
||||
[tx]
|
||||
)
|
||||
const txDocIds = txes.map(({ doc }) => doc?._id).filter(Boolean) as Array<Ref<Doc>>
|
||||
const ctx = txes.pop()
|
||||
if (ctx !== undefined) {
|
||||
ctx.txes = txes
|
||||
ctx.txDocIds = new Set(txDocIds)
|
||||
}
|
||||
return ctx
|
||||
}
|
||||
|
||||
export async function getExtensions (client: Client, ofClass: Ref<Class<Doc>>): Promise<ActivityExtension[]> {
|
||||
return await client.findAll(activity.class.ActivityExtension, { ofClass })
|
||||
}
|
||||
|
||||
@@ -14,23 +14,24 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@hcengineering/platform-rig": "^0.6.0",
|
||||
"@types/jest": "^29.5.5",
|
||||
"@typescript-eslint/eslint-plugin": "^6.11.0",
|
||||
"@typescript-eslint/parser": "^6.11.0",
|
||||
"eslint": "^8.54.0",
|
||||
"eslint-config-standard-with-typescript": "^40.0.0",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-n": "^15.4.0",
|
||||
"eslint-plugin-promise": "^6.1.1",
|
||||
"eslint": "^8.54.0",
|
||||
"prettier": "^3.1.0",
|
||||
"typescript": "^5.2.2",
|
||||
"jest": "^29.7.0",
|
||||
"prettier": "^3.1.0",
|
||||
"prettier-plugin-svelte": "^3.1.0",
|
||||
"ts-jest": "^29.1.1",
|
||||
"@types/jest": "^29.5.5",
|
||||
"prettier-plugin-svelte": "^3.1.0"
|
||||
"typescript": "^5.2.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hcengineering/platform": "^0.6.9",
|
||||
"@hcengineering/core": "^0.6.28",
|
||||
"@hcengineering/notification": "^0.6.16",
|
||||
"@hcengineering/platform": "^0.6.9",
|
||||
"@hcengineering/ui": "^0.6.11"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,10 +27,11 @@ import type {
|
||||
TxMixin,
|
||||
TxUpdateDoc
|
||||
} from '@hcengineering/core'
|
||||
import type { Asset, IntlString, Plugin, Resource } from '@hcengineering/platform'
|
||||
import type { Asset, IntlString, Plugin } from '@hcengineering/platform'
|
||||
import { plugin } from '@hcengineering/platform'
|
||||
import type { AnyComponent } from '@hcengineering/ui'
|
||||
|
||||
// TODO: remove TxViewlet
|
||||
/**
|
||||
* Define an display for all transaction kinds for particular class.
|
||||
* @public
|
||||
@@ -61,6 +62,7 @@ export interface TxViewlet extends Doc {
|
||||
hideOnRemove?: boolean
|
||||
}
|
||||
|
||||
// TODO: remove DisplayTx
|
||||
/**
|
||||
* Transaction being displayed.
|
||||
* @public
|
||||
@@ -96,69 +98,27 @@ export interface DisplayTx {
|
||||
originTx: TxCUD<Doc>
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface ActivityFilter extends Doc {
|
||||
label: IntlString
|
||||
filter: Resource<(tx: DisplayTx, _class?: Ref<Doc>) => boolean>
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface ExtraActivityComponent extends Class<Doc> {
|
||||
component: AnyComponent
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export const activityId = 'activity' as Plugin
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export type ActivityExtensionKind = 'footer' | 'action' | 'input'
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface ActivityExtension extends Doc {
|
||||
ofClass: Ref<Class<Doc>>
|
||||
components?: Partial<Record<ActivityExtensionKind, AnyComponent>>
|
||||
isMention?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface TxMention extends Class<Doc> {}
|
||||
|
||||
export default plugin(activityId, {
|
||||
icon: {
|
||||
Activity: '' as Asset
|
||||
},
|
||||
string: {
|
||||
Delete: '' as IntlString,
|
||||
Edit: '' as IntlString,
|
||||
Edited: '' as IntlString,
|
||||
Activity: '' as IntlString,
|
||||
Changed: '' as IntlString,
|
||||
To: '' as IntlString,
|
||||
Unset: '' as IntlString,
|
||||
Added: '' as IntlString,
|
||||
Changed: '' as IntlString,
|
||||
Edited: '' as IntlString,
|
||||
From: '' as IntlString,
|
||||
Removed: '' as IntlString,
|
||||
NewestFirst: '' as IntlString
|
||||
},
|
||||
mixin: {
|
||||
ExtraActivityComponent: '' as Ref<Class<ExtraActivityComponent>>
|
||||
To: '' as IntlString,
|
||||
Unset: '' as IntlString
|
||||
},
|
||||
class: {
|
||||
TxViewlet: '' as Ref<Class<TxViewlet>>,
|
||||
ActivityFilter: '' as Ref<Class<ActivityFilter>>,
|
||||
ActivityExtension: '' as Ref<Class<ActivityExtension>>
|
||||
TxViewlet: '' as Ref<Class<TxViewlet>>
|
||||
},
|
||||
component: {
|
||||
Activity: '' as AnyComponent
|
||||
|
||||
@@ -14,43 +14,44 @@
|
||||
"build:watch": "tsc --noEmit --outDir ./dist_cache"
|
||||
},
|
||||
"devDependencies": {
|
||||
"svelte-loader": "^3.1.9",
|
||||
"sass": "^1.53.0",
|
||||
"svelte-preprocess": "^5.1.0",
|
||||
"@hcengineering/platform-rig": "^0.6.0",
|
||||
"@types/jest": "^29.5.5",
|
||||
"@typescript-eslint/eslint-plugin": "^6.11.0",
|
||||
"@typescript-eslint/parser": "^6.11.0",
|
||||
"eslint": "^8.54.0",
|
||||
"eslint-config-standard-with-typescript": "^40.0.0",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-n": "^15.4.0",
|
||||
"eslint-plugin-promise": "^6.1.1",
|
||||
"eslint-plugin-svelte": "^2.34.0",
|
||||
"prettier-plugin-svelte": "^3.1.0",
|
||||
"eslint": "^8.54.0",
|
||||
"prettier": "^3.1.0",
|
||||
"svelte-check": "^3.6.0",
|
||||
"typescript": "^5.2.2",
|
||||
"jest": "^29.7.0",
|
||||
"prettier": "^3.1.0",
|
||||
"prettier-plugin-svelte": "^3.1.0",
|
||||
"sass": "^1.53.0",
|
||||
"svelte-check": "^3.6.0",
|
||||
"svelte-eslint-parser": "^0.33.1",
|
||||
"svelte-loader": "^3.1.9",
|
||||
"svelte-preprocess": "^5.1.0",
|
||||
"ts-jest": "^29.1.1",
|
||||
"@types/jest": "^29.5.5",
|
||||
"svelte-eslint-parser": "^0.33.1"
|
||||
"typescript": "^5.2.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hcengineering/platform": "^0.6.9",
|
||||
"svelte": "^4.2.5",
|
||||
"@hcengineering/ui": "^0.6.11",
|
||||
"@hcengineering/presentation": "^0.6.2",
|
||||
"@hcengineering/activity": "^0.6.0",
|
||||
"@hcengineering/attachment": "^0.6.9",
|
||||
"@hcengineering/contact": "^0.6.20",
|
||||
"@hcengineering/core": "^0.6.28",
|
||||
"@hcengineering/login": "^0.6.8",
|
||||
"@hcengineering/notification": "^0.6.16",
|
||||
"@hcengineering/panel": "^0.6.15",
|
||||
"@hcengineering/platform": "^0.6.9",
|
||||
"@hcengineering/preference": "^0.6.9",
|
||||
"@hcengineering/presentation": "^0.6.2",
|
||||
"@hcengineering/text-editor": "^0.6.0",
|
||||
"@hcengineering/ui": "^0.6.11",
|
||||
"@hcengineering/view": "^0.6.9",
|
||||
"@hcengineering/view-resources": "^0.6.0",
|
||||
"@hcengineering/panel": "^0.6.15",
|
||||
"@hcengineering/text-editor": "^0.6.0",
|
||||
"@hcengineering/login": "^0.6.8",
|
||||
"filesize": "^8.0.3",
|
||||
"@hcengineering/preference": "^0.6.9",
|
||||
"@hcengineering/activity": "^0.6.0"
|
||||
"svelte": "^4.2.5"
|
||||
},
|
||||
"repository": "https://github.com/hcengineering/anticrm",
|
||||
"publishConfig": {
|
||||
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
<!--
|
||||
// Copyright © 2023 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 { DocUpdateMessage } from '@hcengineering/notification'
|
||||
import { Attachment } from '@hcengineering/attachment'
|
||||
|
||||
import AttachmentPresenter from '../AttachmentPresenter.svelte'
|
||||
import RemovedAttachmentPresenter from '../RemovedAttachmentPresenter.svelte'
|
||||
|
||||
export let message: DocUpdateMessage
|
||||
export let value: Attachment | undefined
|
||||
</script>
|
||||
|
||||
{#if value}
|
||||
{#if message.action === 'remove'}
|
||||
<RemovedAttachmentPresenter {value} />
|
||||
{:else}
|
||||
<AttachmentPresenter {value} />
|
||||
{/if}
|
||||
{/if}
|
||||
@@ -14,17 +14,12 @@
|
||||
//
|
||||
|
||||
import attachment, { type Attachment } from '@hcengineering/attachment'
|
||||
import {
|
||||
type ObjQueryType,
|
||||
SortingOrder,
|
||||
type SortingQuery,
|
||||
type Markup,
|
||||
type Ref,
|
||||
type Doc
|
||||
} from '@hcengineering/core'
|
||||
import { type ObjQueryType, SortingOrder, type SortingQuery, type Markup } from '@hcengineering/core'
|
||||
import { type IntlString, type Resources } from '@hcengineering/platform'
|
||||
import preference from '@hcengineering/preference'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import notification, { type ActivityMessage, type DocUpdateMessage } from '@hcengineering/notification'
|
||||
|
||||
import TxAttachmentCreate from './components/activity/TxAttachmentCreate.svelte'
|
||||
import AddAttachment from './components/AddAttachment.svelte'
|
||||
import AttachmentDocList from './components/AttachmentDocList.svelte'
|
||||
@@ -44,8 +39,8 @@ import AccordionEditor from './components/AccordionEditor.svelte'
|
||||
import IconUploadDuo from './components/icons/UploadDuo.svelte'
|
||||
import IconAttachment from './components/icons/Attachment.svelte'
|
||||
import { deleteFile, uploadFile } from './utils'
|
||||
import { type DisplayTx } from '@hcengineering/activity'
|
||||
import AttachmentPreview from './components/AttachmentPreview.svelte'
|
||||
import NotificationAttachmentChanged from './components/notification/NotificationAttachmentChanged.svelte'
|
||||
|
||||
export {
|
||||
AddAttachment,
|
||||
@@ -239,8 +234,12 @@ export async function DeleteAttachment (attach: Attachment): Promise<void> {
|
||||
)
|
||||
}
|
||||
|
||||
export function attachmentsFilter (tx: DisplayTx, _class?: Ref<Doc>): boolean {
|
||||
return tx.tx.objectClass === attachment.class.Attachment
|
||||
export function attachmentsFilter (message: ActivityMessage): boolean {
|
||||
if (message._class === notification.class.DocUpdateMessage) {
|
||||
return (message as DocUpdateMessage).objectClass === attachment.class.Attachment
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
export default async (): Promise<Resources> => ({
|
||||
@@ -255,6 +254,9 @@ export default async (): Promise<Resources> => ({
|
||||
FileBrowser,
|
||||
Photos
|
||||
},
|
||||
notification: {
|
||||
NotificationAttachmentChanged
|
||||
},
|
||||
activity: {
|
||||
TxAttachmentCreate
|
||||
},
|
||||
|
||||
@@ -30,18 +30,18 @@
|
||||
"prettier-plugin-svelte": "^3.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hcengineering/platform": "^0.6.9",
|
||||
"@hcengineering/core": "^0.6.28",
|
||||
"@hcengineering/preference": "^0.6.9",
|
||||
"@hcengineering/tags": "^0.6.12",
|
||||
"@hcengineering/contact": "^0.6.20",
|
||||
"@hcengineering/chunter": "^0.6.12",
|
||||
"@hcengineering/attachment": "^0.6.9",
|
||||
"fast-equals": "^2.0.3",
|
||||
"qs": "~6.11.0",
|
||||
"@hcengineering/contact": "^0.6.20",
|
||||
"@hcengineering/core": "^0.6.28",
|
||||
"@hcengineering/gmail": "^0.6.15",
|
||||
"@hcengineering/notification": "^0.6.16",
|
||||
"@hcengineering/platform": "^0.6.9",
|
||||
"@hcengineering/preference": "^0.6.9",
|
||||
"@hcengineering/recruit": "^0.6.21",
|
||||
"@hcengineering/task": "^0.6.13"
|
||||
"@hcengineering/tags": "^0.6.12",
|
||||
"@hcengineering/task": "^0.6.13",
|
||||
"fast-equals": "^2.0.3",
|
||||
"qs": "~6.11.0"
|
||||
},
|
||||
"repository": "https://github.com/hcengineering/anticrm",
|
||||
"publishConfig": {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import attachment, { Attachment } from '@hcengineering/attachment'
|
||||
import chunter, { Comment } from '@hcengineering/chunter'
|
||||
import contact, { Channel, combineName, Contact, Employee, PersonAccount } from '@hcengineering/contact'
|
||||
import core, {
|
||||
Account,
|
||||
@@ -41,6 +40,7 @@ import {
|
||||
LoginInfo
|
||||
} from './types'
|
||||
import { convert, ConvertResult } from './utils'
|
||||
import notification, { ChatMessage } from '@hcengineering/notification'
|
||||
|
||||
async function updateDoc (client: ApplyOperations, doc: Doc, raw: Doc | Data<Doc>, date: Timestamp): Promise<Doc> {
|
||||
// We need to update fields if they are different.
|
||||
@@ -785,9 +785,9 @@ async function downloadComments (
|
||||
order: { ID: ops.direction }
|
||||
})
|
||||
for (const it of commentsData.result) {
|
||||
const c: Comment & BitrixSyncDoc = {
|
||||
const c: ChatMessage & BitrixSyncDoc = {
|
||||
_id: generateId(),
|
||||
_class: chunter.class.Comment,
|
||||
_class: notification.class.ChatMessage,
|
||||
message: processComment(it.COMMENT as string),
|
||||
bitrixId: `${it.ID as string}`,
|
||||
type: it.ENTITY_TYPE,
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
<script lang="ts">
|
||||
import { AttachmentDroppable, AttachmentsPresenter } from '@hcengineering/attachment-resources'
|
||||
import type { Card } from '@hcengineering/board'
|
||||
import { CommentsPresenter } from '@hcengineering/chunter-resources'
|
||||
import { Employee } from '@hcengineering/contact'
|
||||
import type { Ref, WithLookup } from '@hcengineering/core'
|
||||
import notification from '@hcengineering/notification'
|
||||
@@ -41,6 +40,7 @@
|
||||
import { hasDate, openCardPanel, updateCard, updateCardMembers } from '../utils/CardUtils'
|
||||
import CheckListsPresenter from './presenters/ChecklistsPresenter.svelte'
|
||||
import NotificationPresenter from './presenters/NotificationPresenter.svelte'
|
||||
import { ChatMessagesPresenter } from '@hcengineering/notification-resources'
|
||||
|
||||
export let object: WithLookup<Card>
|
||||
|
||||
@@ -196,7 +196,7 @@
|
||||
{/if}
|
||||
{#if (object.comments ?? 0) > 0}
|
||||
<div class="float-left">
|
||||
<CommentsPresenter value={object.comments} {object} />
|
||||
<ChatMessagesPresenter value={object.comments} {object} size="small" />
|
||||
</div>
|
||||
{/if}
|
||||
{#if (object.todoItems ?? 0) > 0}
|
||||
|
||||
@@ -17,7 +17,8 @@
|
||||
"About": "About",
|
||||
"Members": "Members",
|
||||
"NoMembers": "No members",
|
||||
"MentionedIn": "mentioned you in ",
|
||||
"MentionedIn": "Mentioned you in ",
|
||||
"Mentioned": "Mentioned",
|
||||
"ContactInfo": "Contact Info",
|
||||
"Content": "Content",
|
||||
"Comment": "Comment",
|
||||
@@ -67,8 +68,6 @@
|
||||
"Messages": "Messages",
|
||||
"NoResults": "No results",
|
||||
"CopyLink": "Copy link",
|
||||
"FilterComments": "Comments",
|
||||
"FilterPinnedComments": "Pinned comments",
|
||||
"FilterBacklinks": "Backlinks",
|
||||
"DM": "Direct message",
|
||||
"DMNotification": "Sent you a message",
|
||||
@@ -80,6 +79,7 @@
|
||||
"NoMessages": "There are no messages yet",
|
||||
"DirectNotificationTitle": "{senderName}",
|
||||
"DirectNotificationBody": "{message}",
|
||||
"AddCommentPlaceholder": "Add comment..."
|
||||
"AddCommentPlaceholder": "Add comment...",
|
||||
"Reacted": "Reacted"
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,8 @@
|
||||
"About": "Информация",
|
||||
"Members": "Участники",
|
||||
"NoMembers": "Нет участников",
|
||||
"MentionedIn": "упомянул(а) вас в",
|
||||
"MentionedIn": "Упомянул(а) вас в",
|
||||
"Mentioned": "Упомянул(а)",
|
||||
"ContactInfo": "Контактная информация",
|
||||
"Content": "Содержимое",
|
||||
"Comment": "Комментарий",
|
||||
@@ -67,8 +68,6 @@
|
||||
"Messages": "Сообщения",
|
||||
"NoResults": "Нет результатов",
|
||||
"CopyLink": "Копировать ссылку",
|
||||
"FilterComments": "Комментарии",
|
||||
"FilterPinnedComments": "Закрепленные комментарии",
|
||||
"FilterBacklinks": "Упоминания",
|
||||
"DM": "Личное сообщение",
|
||||
"DMNotification": "Отправил сообщение",
|
||||
@@ -80,6 +79,7 @@
|
||||
"NoMessages": "Сообщений пока нет",
|
||||
"DirectNotificationTitle": "{senderName}",
|
||||
"DirectNotificationBody": "{message}",
|
||||
"AddCommentPlaceholder": "Добавить комментарий..."
|
||||
"AddCommentPlaceholder": "Добавить комментарий...",
|
||||
"Reacted": "Отреагировал(а)"
|
||||
}
|
||||
}
|
||||
@@ -14,48 +14,48 @@
|
||||
"build:watch": "tsc --noEmit --outDir ./dist_cache"
|
||||
},
|
||||
"devDependencies": {
|
||||
"svelte-loader": "^3.1.9",
|
||||
"sass": "^1.53.0",
|
||||
"svelte-preprocess": "^5.1.0",
|
||||
"@hcengineering/platform-rig": "^0.6.0",
|
||||
"@types/jest": "^29.5.5",
|
||||
"@typescript-eslint/eslint-plugin": "^6.11.0",
|
||||
"@typescript-eslint/parser": "^6.11.0",
|
||||
"eslint": "^8.54.0",
|
||||
"eslint-config-standard-with-typescript": "^40.0.0",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-n": "^15.4.0",
|
||||
"eslint-plugin-promise": "^6.1.1",
|
||||
"eslint-plugin-svelte": "^2.34.0",
|
||||
"prettier-plugin-svelte": "^3.1.0",
|
||||
"eslint": "^8.54.0",
|
||||
"prettier": "^3.1.0",
|
||||
"svelte-check": "^3.6.0",
|
||||
"typescript": "^5.2.2",
|
||||
"jest": "^29.7.0",
|
||||
"prettier": "^3.1.0",
|
||||
"prettier-plugin-svelte": "^3.1.0",
|
||||
"sass": "^1.53.0",
|
||||
"svelte-check": "^3.6.0",
|
||||
"svelte-eslint-parser": "^0.33.1",
|
||||
"svelte-loader": "^3.1.9",
|
||||
"svelte-preprocess": "^5.1.0",
|
||||
"ts-jest": "^29.1.1",
|
||||
"@types/jest": "^29.5.5",
|
||||
"svelte-eslint-parser": "^0.33.1"
|
||||
"typescript": "^5.2.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hcengineering/core": "^0.6.28",
|
||||
"@hcengineering/platform": "^0.6.9",
|
||||
"@hcengineering/ui": "^0.6.11",
|
||||
"@hcengineering/presentation": "^0.6.2",
|
||||
"@hcengineering/chunter": "^0.6.12",
|
||||
"@hcengineering/login": "^0.6.8",
|
||||
"svelte": "^4.2.5",
|
||||
"fast-equals": "^2.0.3",
|
||||
"@hcengineering/text-editor": "^0.6.0",
|
||||
"@hcengineering/contact": "^0.6.20",
|
||||
"@hcengineering/contact-resources": "^0.6.0",
|
||||
"@hcengineering/notification": "^0.6.16",
|
||||
"@hcengineering/notification-resources": "^0.6.0",
|
||||
"@hcengineering/activity": "^0.6.0",
|
||||
"@hcengineering/attachment": "^0.6.9",
|
||||
"@hcengineering/attachment-resources": "^0.6.0",
|
||||
"@hcengineering/view-resources": "^0.6.0",
|
||||
"@hcengineering/view": "^0.6.9",
|
||||
"@hcengineering/workbench": "^0.6.9",
|
||||
"@hcengineering/chunter": "^0.6.12",
|
||||
"@hcengineering/contact": "^0.6.20",
|
||||
"@hcengineering/contact-resources": "^0.6.0",
|
||||
"@hcengineering/core": "^0.6.28",
|
||||
"@hcengineering/login": "^0.6.8",
|
||||
"@hcengineering/notification": "^0.6.16",
|
||||
"@hcengineering/notification-resources": "^0.6.0",
|
||||
"@hcengineering/platform": "^0.6.9",
|
||||
"@hcengineering/preference": "^0.6.9",
|
||||
"@hcengineering/workbench-resources": "^0.6.1"
|
||||
"@hcengineering/presentation": "^0.6.2",
|
||||
"@hcengineering/text-editor": "^0.6.0",
|
||||
"@hcengineering/ui": "^0.6.11",
|
||||
"@hcengineering/view": "^0.6.9",
|
||||
"@hcengineering/view-resources": "^0.6.0",
|
||||
"@hcengineering/workbench": "^0.6.9",
|
||||
"@hcengineering/workbench-resources": "^0.6.1",
|
||||
"fast-equals": "^2.0.3",
|
||||
"svelte": "^4.2.5"
|
||||
}
|
||||
}
|
||||
|
||||
+10
-3
@@ -16,9 +16,16 @@
|
||||
import type { Backlink } from '@hcengineering/chunter'
|
||||
import { MessageViewer } from '@hcengineering/presentation'
|
||||
|
||||
// export let tx: TxCreateDoc<Backlink>
|
||||
export let value: Backlink
|
||||
// export let edit: boolean = false
|
||||
</script>
|
||||
|
||||
<MessageViewer message={value.message} />
|
||||
<div class="root">
|
||||
<MessageViewer message={value?.message} />
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.root {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
</style>
|
||||
+2
-7
@@ -20,24 +20,19 @@
|
||||
import { getObjectPresenter } from '@hcengineering/view-resources'
|
||||
|
||||
export let value: Backlink
|
||||
export let inline = true
|
||||
|
||||
const client = getClient()
|
||||
let presenter: AttributeModel | undefined
|
||||
|
||||
const docQuery = createQuery()
|
||||
// const targetQuery = createQuery()
|
||||
let doc: Doc | undefined
|
||||
// let target: Doc | undefined
|
||||
|
||||
$: value.backlinkClass != null &&
|
||||
docQuery.query(value.backlinkClass, { _id: value.backlinkId }, (r) => {
|
||||
doc = r.shift()
|
||||
})
|
||||
|
||||
// $: targetQuery.query(value.attachedToClass, { _id: value.attachedTo }, (r) => {
|
||||
// target = r.shift()
|
||||
// })
|
||||
|
||||
$: if (doc !== undefined) {
|
||||
getObjectPresenter(client, doc._class, { key: '' }).then((p) => {
|
||||
presenter = p
|
||||
@@ -47,6 +42,6 @@
|
||||
|
||||
{#if presenter}
|
||||
<span class="labels-row">
|
||||
<svelte:component this={presenter.presenter} value={doc} inline />
|
||||
<svelte:component this={presenter.presenter} value={doc} {inline} embedded shouldShowAvatar={false} />
|
||||
</span>
|
||||
{/if}
|
||||
@@ -20,6 +20,8 @@
|
||||
|
||||
export let value: Channel
|
||||
export let inline: boolean = false
|
||||
export let shouldShowAvatar = true
|
||||
|
||||
const client = getClient()
|
||||
|
||||
$: icon = client.getHierarchy().getClass(value._class).icon
|
||||
@@ -28,7 +30,7 @@
|
||||
{#if value}
|
||||
<NavLink app={chunterId} space={value._id}>
|
||||
<div class="flex-presenter">
|
||||
{#if !inline}
|
||||
{#if !inline && shouldShowAvatar}
|
||||
<div class="icon">
|
||||
{#if icon}
|
||||
<Icon {icon} size={'small'} />
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
<!--
|
||||
// Copyright © 2020, 2021 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.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { getName, Person } from '@hcengineering/contact'
|
||||
import { Avatar } from '@hcengineering/contact-resources'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
|
||||
interface IMessage {
|
||||
text: string
|
||||
createDate: Date
|
||||
}
|
||||
|
||||
export let user: Person
|
||||
export let message: IMessage
|
||||
const client = getClient()
|
||||
</script>
|
||||
|
||||
<div class="flex-nowrap">
|
||||
<div class="avatar"><Avatar size={'medium'} avatar={user.avatar} name={user.name} /></div>
|
||||
<div class="flex-col-stretch message">
|
||||
<div class="header">{getName(client.getHierarchy(), user)}<span>{message.createDate}</span></div>
|
||||
<div class="text">{message.text}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.avatar {
|
||||
margin-right: 1rem;
|
||||
}
|
||||
.message {
|
||||
margin-right: 1.25rem;
|
||||
|
||||
.header {
|
||||
margin-bottom: 0.25rem;
|
||||
font-weight: 500;
|
||||
font-size: 1rem;
|
||||
line-height: 150%;
|
||||
color: var(--caption-color);
|
||||
|
||||
span {
|
||||
margin-left: 0.5rem;
|
||||
font-weight: 400;
|
||||
font-size: 0.875rem;
|
||||
color: var(--dark-color);
|
||||
}
|
||||
}
|
||||
|
||||
.text {
|
||||
line-height: 150%;
|
||||
color: var(--content-color);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,134 +0,0 @@
|
||||
<!--
|
||||
// Copyright © 2020, 2021 Anticrm Platform Contributors.
|
||||
// Copyright © 2021, 2022 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 { AttachmentRefInput } from '@hcengineering/attachment-resources'
|
||||
import { Comment } from '@hcengineering/chunter'
|
||||
import { AttachedData, Doc, generateId, Ref } from '@hcengineering/core'
|
||||
import { createQuery, DraftController, draftsStore, getClient } from '@hcengineering/presentation'
|
||||
import chunter from '../plugin'
|
||||
|
||||
export let object: Doc
|
||||
export let shouldSaveDraft: boolean = true
|
||||
export let focusIndex: number = -1
|
||||
export let boundary: HTMLElement | undefined = undefined
|
||||
|
||||
const client = getClient()
|
||||
const _class = chunter.class.Comment
|
||||
|
||||
type CommentDraft = AttachedData<Comment> & { _id: Ref<Comment> }
|
||||
|
||||
const draftKey = `${object._id}_comment`
|
||||
const draftController = new DraftController<CommentDraft>(draftKey)
|
||||
|
||||
const empty = {
|
||||
message: '<p></p>',
|
||||
attachments: 0
|
||||
}
|
||||
|
||||
let commentInputBox: AttachmentRefInput
|
||||
const draftComment = shouldSaveDraft ? $draftsStore[draftKey] : undefined
|
||||
|
||||
let comment: CommentDraft = draftComment ?? getDefault()
|
||||
let _id: Ref<Comment> = comment._id
|
||||
let inputContent: string = comment.message
|
||||
|
||||
const createdQuery = createQuery()
|
||||
|
||||
$: createdQuery.query(chunter.class.Comment, { _id }, (docs) => {
|
||||
if (docs.length > 0) {
|
||||
// Ouch we have got comment with same id created already.
|
||||
comment = getDefault()
|
||||
_id = comment._id
|
||||
commentInputBox.removeDraft(false)
|
||||
}
|
||||
})
|
||||
|
||||
function objectChange (object: CommentDraft, empty: any) {
|
||||
if (shouldSaveDraft) {
|
||||
draftController.save(object, empty)
|
||||
}
|
||||
}
|
||||
|
||||
$: objectChange(comment, empty)
|
||||
|
||||
function getDefault (): CommentDraft {
|
||||
return {
|
||||
_id: generateId(),
|
||||
...empty
|
||||
}
|
||||
}
|
||||
|
||||
async function onUpdate (event: CustomEvent) {
|
||||
if (!shouldSaveDraft) {
|
||||
return
|
||||
}
|
||||
const { message, attachments } = event.detail
|
||||
if (comment) {
|
||||
comment.message = message
|
||||
comment.attachments = message
|
||||
} else {
|
||||
comment = {
|
||||
_id,
|
||||
message,
|
||||
attachments
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let loading = false
|
||||
|
||||
async function onMessage (event: CustomEvent) {
|
||||
loading = true
|
||||
const { message, attachments } = event.detail
|
||||
try {
|
||||
draftController.remove()
|
||||
commentInputBox.removeDraft(false)
|
||||
await client.addCollection<Doc, Comment>(
|
||||
_class,
|
||||
object.space,
|
||||
object._id,
|
||||
object._class,
|
||||
'comments',
|
||||
{ message, attachments },
|
||||
_id
|
||||
)
|
||||
|
||||
// Remove draft from Local Storage
|
||||
comment = getDefault()
|
||||
_id = comment._id
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
} finally {
|
||||
loading = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<AttachmentRefInput
|
||||
{focusIndex}
|
||||
bind:this={commentInputBox}
|
||||
bind:content={inputContent}
|
||||
{_class}
|
||||
space={object.space}
|
||||
bind:objectId={_id}
|
||||
{shouldSaveDraft}
|
||||
{boundary}
|
||||
on:message={onMessage}
|
||||
on:update={onUpdate}
|
||||
on:focus
|
||||
on:blur
|
||||
bind:loading
|
||||
/>
|
||||
@@ -1,95 +0,0 @@
|
||||
<!--
|
||||
// Copyright © 2023 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.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import contact, { PersonAccount } from '@hcengineering/contact'
|
||||
import chunter, { Backlink, Comment } from '@hcengineering/chunter'
|
||||
import view, { ObjectPanel } from '@hcengineering/view'
|
||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||
import { AnyComponent, Component, Loading } from '@hcengineering/ui'
|
||||
import { Class, Doc, Ref, getCurrentAccount } from '@hcengineering/core'
|
||||
|
||||
export let _id: Ref<Comment> | undefined
|
||||
export let embedded: boolean = true
|
||||
|
||||
const client = getClient()
|
||||
const hierarchy = client.getHierarchy()
|
||||
|
||||
let comment: Comment | undefined
|
||||
const commentQuery = createQuery()
|
||||
let loadingComment = true
|
||||
|
||||
$: if (_id !== undefined) {
|
||||
commentQuery.query(chunter.class.Comment, { _id }, (res) => {
|
||||
;[comment] = res
|
||||
loadingComment = false
|
||||
})
|
||||
} else {
|
||||
commentQuery.unsubscribe()
|
||||
loadingComment = false
|
||||
}
|
||||
|
||||
let backlinks: Backlink[] = []
|
||||
const backlinksQuery = createQuery()
|
||||
let loadingBacklinks = true
|
||||
|
||||
const mePerson = (getCurrentAccount() as PersonAccount).person
|
||||
let isMeMentioned = false
|
||||
|
||||
$: if (_id !== undefined) {
|
||||
backlinksQuery.query(chunter.class.Backlink, { attachedDocId: _id }, (res) => {
|
||||
backlinks = res
|
||||
for (const backlink of backlinks) {
|
||||
if (hierarchy.isDerived(backlink.attachedToClass, contact.class.Person) && mePerson === backlink.attachedTo) {
|
||||
isMeMentioned = true
|
||||
break
|
||||
}
|
||||
}
|
||||
loadingBacklinks = false
|
||||
})
|
||||
} else {
|
||||
backlinksQuery.unsubscribe()
|
||||
loadingBacklinks = false
|
||||
}
|
||||
|
||||
let attachedDocId: Ref<Doc> | undefined
|
||||
let attachedDocClass: Ref<Class<Doc>> | undefined
|
||||
|
||||
$: loading = loadingComment || loadingBacklinks
|
||||
|
||||
let component: AnyComponent
|
||||
$: getComponent(loading, comment)
|
||||
|
||||
async function getComponent (loading: boolean, comment?: Comment): Promise<void> {
|
||||
if (comment == null || loading) {
|
||||
return
|
||||
}
|
||||
|
||||
let panelComponent: ObjectPanel | undefined
|
||||
if (isMeMentioned) {
|
||||
panelComponent = hierarchy.classHierarchyMixin(comment.attachedToClass, view.mixin.ObjectPanel)
|
||||
}
|
||||
|
||||
component = panelComponent?.component ?? view.component.EditDoc
|
||||
|
||||
attachedDocId = comment.attachedTo
|
||||
attachedDocClass = comment.attachedToClass
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if loading}
|
||||
<Loading />
|
||||
{:else if component && attachedDocId && attachedDocClass}
|
||||
<Component is={component} props={{ _id: attachedDocId, _class: attachedDocClass, embedded }} on:close />
|
||||
{/if}
|
||||
@@ -1,117 +0,0 @@
|
||||
<!--
|
||||
// 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 { AttachmentDocList } from '@hcengineering/attachment-resources'
|
||||
import type { Comment } from '@hcengineering/chunter'
|
||||
import { Person, PersonAccount, getName } from '@hcengineering/contact'
|
||||
import { Avatar, personAccountByIdStore, personByIdStore } from '@hcengineering/contact-resources'
|
||||
import { IdMap, Ref } from '@hcengineering/core'
|
||||
import { MessageViewer, getClient } from '@hcengineering/presentation'
|
||||
import { Icon, ShowMore, TimeSince } from '@hcengineering/ui'
|
||||
import { LinkPresenter, ObjectPresenter } from '@hcengineering/view-resources'
|
||||
import view from '@hcengineering/view'
|
||||
|
||||
export let value: Comment
|
||||
export let inline: boolean = false
|
||||
// export let disabled = false
|
||||
export let inbox: boolean = false
|
||||
|
||||
const client = getClient()
|
||||
|
||||
async function getEmployee (
|
||||
value: Comment,
|
||||
employees: IdMap<Person>,
|
||||
accounts: IdMap<PersonAccount>
|
||||
): Promise<Person | undefined> {
|
||||
const acc = accounts.get(value.modifiedBy as Ref<PersonAccount>)
|
||||
if (acc !== undefined) {
|
||||
const emp = employees.get(acc.person)
|
||||
return emp
|
||||
}
|
||||
}
|
||||
|
||||
$: links = getLinks(value.message)
|
||||
|
||||
function getLinks (content: string): HTMLLinkElement[] {
|
||||
const parser = new DOMParser()
|
||||
const parent = parser.parseFromString(content, 'text/html').firstChild?.childNodes[1] as HTMLElement
|
||||
return parseLinks(parent.childNodes)
|
||||
}
|
||||
|
||||
function parseLinks (nodes: NodeListOf<ChildNode>): HTMLLinkElement[] {
|
||||
const res: HTMLLinkElement[] = []
|
||||
nodes.forEach((p) => {
|
||||
if (p.nodeType !== Node.TEXT_NODE) {
|
||||
if (p.nodeName === 'A') {
|
||||
res.push(p as HTMLLinkElement)
|
||||
}
|
||||
res.push(...parseLinks(p.childNodes))
|
||||
}
|
||||
})
|
||||
return res
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if inline}
|
||||
{#if !inbox}
|
||||
<ObjectPresenter _class={value.attachedToClass} objectId={value.attachedTo} accent noUnderline />
|
||||
{:else}
|
||||
<MessageViewer message={value.message} />
|
||||
{/if}
|
||||
{:else}
|
||||
<div class="flex-row-top">
|
||||
{#await getEmployee(value, $personByIdStore, $personAccountByIdStore) then employee}
|
||||
<div class="avatar">
|
||||
<Avatar size={'medium'} avatar={employee?.avatar} name={employee?.name} />
|
||||
</div>
|
||||
<div class="flex-grow flex-col select-text">
|
||||
<div class="header">
|
||||
<div class="fs-title">
|
||||
{#if employee}{getName(client.getHierarchy(), employee)}{/if}
|
||||
</div>
|
||||
<div class="content-dark-color ml-4 flex-row-center">
|
||||
{#if value.pinned}
|
||||
<Icon icon={view.icon.Pin} size={'small'} fill="#3265cb" />
|
||||
{/if}
|
||||
<div class="ml-1">
|
||||
<TimeSince value={value.modifiedOn} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ShowMore fixed>
|
||||
<MessageViewer message={value.message} />
|
||||
<AttachmentDocList {value} />
|
||||
{#each links as link}
|
||||
<LinkPresenter {link} />
|
||||
{/each}
|
||||
</ShowMore>
|
||||
</div>
|
||||
{/await}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
.avatar {
|
||||
margin-right: 1rem;
|
||||
min-width: 2.25rem;
|
||||
}
|
||||
.header {
|
||||
display: inline-flex;
|
||||
justify-content: space-between;
|
||||
align-items: baseline;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
</style>
|
||||
@@ -19,7 +19,7 @@
|
||||
import type { ChunterMessage, ChunterMessageExtension, Message, Reaction } from '@hcengineering/chunter'
|
||||
import { PersonAccount } from '@hcengineering/contact'
|
||||
import { Avatar, personByIdStore, EmployeePresenter } from '@hcengineering/contact-resources'
|
||||
import { getCurrentAccount, Mixin, Ref, WithLookup } from '@hcengineering/core'
|
||||
import { getCurrentAccount, getDisplayTime, Mixin, Ref, WithLookup } from '@hcengineering/core'
|
||||
import { getResource } from '@hcengineering/platform'
|
||||
import { getClient, MessageViewer } from '@hcengineering/presentation'
|
||||
import ui, { ActionIcon, Button, EmojiPopup, IconMoreV, Label, showPopup, tooltip } from '@hcengineering/ui'
|
||||
@@ -35,7 +35,7 @@
|
||||
|
||||
import { AddMessageToSaved, DeleteMessageFromSaved, UnpinMessage } from '../index'
|
||||
import chunter from '../plugin'
|
||||
import { getLinks, getTime, updateDocReactions } from '../utils'
|
||||
import { getLinks, updateDocReactions } from '../utils'
|
||||
|
||||
export let message: WithLookup<ChunterMessage>
|
||||
export let savedAttachmentsIds: Ref<Attachment>[]
|
||||
@@ -188,9 +188,9 @@
|
||||
{#if employee}
|
||||
<EmployeePresenter value={employee} shouldShowAvatar={false} />
|
||||
{/if}
|
||||
<span class="text-sm">{getTime(message.createdOn ?? 0)}</span>
|
||||
<span class="text-sm">{getDisplayTime(message.createdOn ?? 0)}</span>
|
||||
{#if message.editedOn}
|
||||
<span use:tooltip={{ label: ui.string.TimeTooltip, props: { value: getTime(message.editedOn) } }}>
|
||||
<span use:tooltip={{ label: ui.string.TimeTooltip, props: { value: getDisplayTime(message.editedOn) } }}>
|
||||
<Label label={chunter.string.Edited} />
|
||||
</span>
|
||||
{/if}
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
|
||||
export let value: Message
|
||||
export let inline: boolean = false
|
||||
export let embedded = false
|
||||
export let disabled = false
|
||||
|
||||
const client = getClient()
|
||||
@@ -41,7 +42,7 @@
|
||||
})
|
||||
</script>
|
||||
|
||||
{#if inline}
|
||||
{#if inline || embedded}
|
||||
{#if presenter && doc}
|
||||
<div class="flex-presenter">
|
||||
{#if isThreadMessage}
|
||||
@@ -53,7 +54,7 @@
|
||||
</span>
|
||||
|
||||
{/if}
|
||||
<svelte:component this={presenter.presenter} value={doc} inline {disabled} />
|
||||
<svelte:component this={presenter.presenter} value={doc} {inline} {disabled} shouldShowAvatarr={false} />
|
||||
</div>
|
||||
{/if}
|
||||
{:else}
|
||||
|
||||
@@ -19,13 +19,13 @@
|
||||
import { LinkPresenter } from '@hcengineering/view-resources'
|
||||
import { AttachmentList } from '@hcengineering/attachment-resources'
|
||||
import { getEmbeddedLabel } from '@hcengineering/platform'
|
||||
import { Ref, WithLookup, getCurrentAccount } from '@hcengineering/core'
|
||||
import { Ref, WithLookup, getCurrentAccount, getDisplayTime } from '@hcengineering/core'
|
||||
import { Attachment } from '@hcengineering/attachment'
|
||||
import { EmployeePresenter, personAccountByIdStore, personByIdStore } from '@hcengineering/contact-resources'
|
||||
import { PersonAccount } from '@hcengineering/contact'
|
||||
|
||||
import chunter from '../plugin'
|
||||
import { getLinks, getTime } from '../utils'
|
||||
import { getLinks } from '../utils'
|
||||
|
||||
export let value: WithLookup<ChunterMessage>
|
||||
|
||||
@@ -51,9 +51,9 @@
|
||||
<Label label={chunter.string.You} />
|
||||
{/if}
|
||||
{/if}
|
||||
<span>{getTime(value.createdOn ?? 0)}</span>
|
||||
<span>{getDisplayTime(value.createdOn ?? 0)}</span>
|
||||
{#if value.editedOn}
|
||||
<span use:tooltip={{ label: ui.string.TimeTooltip, props: { value: getTime(value.editedOn) } }}>
|
||||
<span use:tooltip={{ label: ui.string.TimeTooltip, props: { value: getDisplayTime(value.editedOn) } }}>
|
||||
<Label label={getEmbeddedLabel('Edited')} />
|
||||
</span>
|
||||
{/if}
|
||||
|
||||
@@ -2,12 +2,11 @@
|
||||
import chunter, { ChunterMessage } from '@hcengineering/chunter'
|
||||
import { Person, PersonAccount, getName } from '@hcengineering/contact'
|
||||
import { Avatar, personAccountByIdStore, personByIdStore } from '@hcengineering/contact-resources'
|
||||
import { IdMap, Ref, Space } from '@hcengineering/core'
|
||||
import { getDisplayTime, IdMap, Ref, Space } from '@hcengineering/core'
|
||||
import { MessageViewer, createQuery, getClient } from '@hcengineering/presentation'
|
||||
import { IconClose } from '@hcengineering/ui'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { UnpinMessage } from '../index'
|
||||
import { getTime } from '../utils'
|
||||
|
||||
export let space: Ref<Space>
|
||||
|
||||
@@ -70,7 +69,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<MessageViewer message={message.content} />
|
||||
<span class="time">{getTime(message.createdOn ?? 0)}</span>
|
||||
<span class="time">{getDisplayTime(message.createdOn ?? 0)}</span>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
@@ -13,15 +13,20 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { Reaction } from '@hcengineering/chunter'
|
||||
import { Account, Ref } from '@hcengineering/core'
|
||||
import { tooltip } from '@hcengineering/ui'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { Reaction } from '@hcengineering/chunter'
|
||||
import { Account, Doc, Ref } from '@hcengineering/core'
|
||||
import { EmojiPopup, IconAdd, showPopup, tooltip } from '@hcengineering/ui'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
|
||||
import ReactionsTooltip from './ReactionsTooltip.svelte'
|
||||
import { updateDocReactions } from '../utils'
|
||||
|
||||
export let reactions: Reaction[] = []
|
||||
export let object: Doc | undefined = undefined
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
const client = getClient()
|
||||
|
||||
let reactionsAccounts = new Map<string, Ref<Account>[]>()
|
||||
$: {
|
||||
@@ -33,25 +38,43 @@
|
||||
})
|
||||
reactionsAccounts = reactionsAccounts
|
||||
}
|
||||
function getClickHandler (emoji: string) {
|
||||
return (e: CustomEvent) => {
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
dispatch('click', emoji)
|
||||
}
|
||||
}
|
||||
|
||||
function openEmojiPalette (ev: Event) {
|
||||
showPopup(EmojiPopup, {}, ev.target as HTMLElement, (emoji: string) => {
|
||||
updateDocReactions(client, reactions, object, emoji)
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="container">
|
||||
{#each [...reactionsAccounts] as [emoji, accounts]}
|
||||
<div class="reaction over-underline">
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div
|
||||
class="flex-row-center"
|
||||
use:tooltip={{ component: ReactionsTooltip, props: { reactionAccounts: accounts } }}
|
||||
on:click={() => {
|
||||
dispatch('click', emoji)
|
||||
}}
|
||||
>
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div
|
||||
class="item border-radius-1"
|
||||
use:tooltip={{ component: ReactionsTooltip, props: { reactionAccounts: accounts } }}
|
||||
on:click={getClickHandler(emoji)}
|
||||
>
|
||||
<div class="flex-row-center">
|
||||
<div>{emoji}</div>
|
||||
<div class="caption-color counter">{accounts.length}</div>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
{#if object && reactionsAccounts.size > 0}
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<div class="item flex-row-center border-radius-1" class:withoutBackground={true} on:click={openEmojiPalette}>
|
||||
<IconAdd size="small" />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
@@ -59,11 +82,32 @@
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
user-select: none;
|
||||
column-gap: 1rem;
|
||||
column-gap: 0.125rem;
|
||||
row-gap: 0.25rem;
|
||||
}
|
||||
|
||||
.counter {
|
||||
margin-left: 0.25rem;
|
||||
.counter {
|
||||
font-size: 0.75rem;
|
||||
color: var(--theme-dark-color);
|
||||
margin-left: 0.25rem;
|
||||
}
|
||||
|
||||
.item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 2.625rem;
|
||||
height: 1.5rem;
|
||||
background: var(--secondary-button-disabled);
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
border: 1px solid var(--theme-darker-color);
|
||||
}
|
||||
|
||||
&.withoutBackground {
|
||||
background: transparent;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -20,12 +20,15 @@
|
||||
|
||||
import chunter from '../plugin'
|
||||
import { updateDocReactions } from '../utils'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
|
||||
export let object: Doc | undefined = undefined
|
||||
|
||||
const client = getClient()
|
||||
|
||||
const reactionsQuery = createQuery()
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
let reactions: Reaction[] = []
|
||||
|
||||
$: if (object) {
|
||||
@@ -35,11 +38,15 @@
|
||||
}
|
||||
|
||||
function openEmojiPalette (ev: Event) {
|
||||
dispatch('open')
|
||||
showPopup(
|
||||
EmojiPopup,
|
||||
{},
|
||||
ev.target as HTMLElement,
|
||||
(emoji: string) => updateDocReactions(client, reactions, object, emoji),
|
||||
(emoji: string) => {
|
||||
updateDocReactions(client, reactions, object, emoji)
|
||||
dispatch('close')
|
||||
},
|
||||
() => {}
|
||||
)
|
||||
}
|
||||
|
||||
+2
-2
@@ -22,7 +22,7 @@
|
||||
import { updateDocReactions } from '../utils'
|
||||
import chunter from '../plugin'
|
||||
|
||||
export let object: Doc | undefined = undefined
|
||||
export let object: Doc | undefined
|
||||
|
||||
const client = getClient()
|
||||
const reactionsQuery = createQuery()
|
||||
@@ -42,6 +42,6 @@
|
||||
|
||||
{#if reactions.length}
|
||||
<div class="footer flex-col p-inline contrast mt-2">
|
||||
<Reactions {reactions} on:click={handleClick} />
|
||||
<Reactions {reactions} {object} on:click={handleClick} />
|
||||
</div>
|
||||
{/if}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user