mirror of
https://github.com/hcengineering/platform.git
synced 2026-09-22 09:35:00 +02:00
Implement @everyone/@here (#8890)
Signed-off-by: Kristina Fefelova <kristin.fefelova@gmail.com>
This commit is contained in:
@@ -15,11 +15,14 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { showPopup, resizeObserver, deviceOptionsStore as deviceInfo, PopupResult } from '@hcengineering/ui'
|
||||
import { Ref, Class, Doc } from '@hcengineering/core'
|
||||
import { onDestroy, onMount } from 'svelte'
|
||||
import MentionPopup from './MentionPopup.svelte'
|
||||
import DummyPopup from './DummyPopup.svelte'
|
||||
|
||||
export let docClass: Ref<Class<Doc>> | undefined = undefined
|
||||
export let query: string = ''
|
||||
export let multipleMentions: boolean = false
|
||||
export let clientRect: () => ClientRect
|
||||
export let command: (props: any) => void
|
||||
export let close: () => void
|
||||
@@ -115,7 +118,9 @@
|
||||
>
|
||||
<MentionPopup
|
||||
bind:this={searchPopup}
|
||||
{docClass}
|
||||
{query}
|
||||
{multipleMentions}
|
||||
on:close={(evt) => {
|
||||
dispatchItem(evt.detail)
|
||||
}}
|
||||
|
||||
@@ -14,23 +14,95 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { SearchResultDoc } from '@hcengineering/core'
|
||||
import presentation, { SearchResult, reduceCalls, searchFor, type SearchItem } from '@hcengineering/presentation'
|
||||
import core, { SearchResultDoc, Ref, Class, Doc } from '@hcengineering/core'
|
||||
import presentation, {
|
||||
SearchResult,
|
||||
reduceCalls,
|
||||
searchFor,
|
||||
type SearchItem,
|
||||
getClient
|
||||
} from '@hcengineering/presentation'
|
||||
import { Label, ListView, resizeObserver } from '@hcengineering/ui'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import contact from '@hcengineering/contact'
|
||||
import { getReferenceLabel, getReferenceObject } from './extension/reference'
|
||||
import { translate } from '@hcengineering/platform'
|
||||
|
||||
export let query: string = ''
|
||||
export let multipleMentions: boolean = false
|
||||
export let docClass: Ref<Class<Doc>> | undefined = undefined
|
||||
|
||||
let items: SearchItem[] = []
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
const client = getClient()
|
||||
|
||||
let list: ListView
|
||||
let scrollContainer: HTMLElement
|
||||
let selection = 0
|
||||
|
||||
const employeeSearchCategory = client
|
||||
.getModel()
|
||||
.findAllSync(presentation.class.ObjectSearchCategory, { classToSearch: contact.mixin.Employee })[0]
|
||||
|
||||
async function getMultipleEmployeeSearchItems (localQuery: string, lastIndex: number): Promise<SearchItem[]> {
|
||||
if (!multipleMentions) return []
|
||||
const clazz =
|
||||
docClass && client.getHierarchy().hasClass(docClass) ? client.getHierarchy().getClass(docClass) : undefined
|
||||
const docTitle = await translate(clazz?.label ?? core.string.Object, {})
|
||||
|
||||
const everyoneDescription = await translate(contact.string.EveryoneDescription, {
|
||||
title: docTitle.toLowerCase()
|
||||
})
|
||||
const hereDescription = await translate(contact.string.HereDescription, {
|
||||
title: docTitle.toLowerCase()
|
||||
})
|
||||
const everyoneTitle = await translate(contact.string.Everyone, {})
|
||||
const hereTitle = await translate(contact.string.Here, {})
|
||||
return [
|
||||
{
|
||||
num: 0,
|
||||
category: employeeSearchCategory,
|
||||
item: {
|
||||
id: contact.mention.Everyone,
|
||||
title: everyoneTitle,
|
||||
description: everyoneDescription,
|
||||
emojiIcon: '📢',
|
||||
doc: {
|
||||
_id: contact.mention.Everyone,
|
||||
_class: contact.mixin.Employee
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
num: 0,
|
||||
category: employeeSearchCategory,
|
||||
item: {
|
||||
id: contact.mention.Here,
|
||||
title: hereTitle,
|
||||
description: hereDescription,
|
||||
emojiIcon: '📢',
|
||||
doc: {
|
||||
_id: contact.mention.Here,
|
||||
_class: contact.mixin.Employee
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
.filter((it) => it.item.title.toLowerCase().includes(localQuery.toLowerCase()))
|
||||
.map((it, idx) => ({ ...it, num: lastIndex + 1 + idx }))
|
||||
}
|
||||
|
||||
async function handleSelectItem (item: SearchResultDoc): Promise<void> {
|
||||
if ([contact.mention.Here, contact.mention.Everyone].includes(item.id as any)) {
|
||||
dispatch('close', {
|
||||
id: item.doc._id,
|
||||
label: item.title?.toLowerCase() ?? '',
|
||||
objectclass: item.doc._class
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
const obj = (await getReferenceObject(item.doc._class, item.doc._id)) ?? item.doc
|
||||
const label = await getReferenceLabel(obj._class, obj._id)
|
||||
dispatch('close', {
|
||||
@@ -73,7 +145,13 @@
|
||||
const updateItems = reduceCalls(async function (localQuery: string): Promise<void> {
|
||||
const r = await searchFor('mention', localQuery)
|
||||
if (r.query === query) {
|
||||
items = r.items
|
||||
const latestIndex = r.items.findLastIndex((it) => it.category.classToSearch === contact.mixin.Employee)
|
||||
const multipleEmployeeSearchItems = await getMultipleEmployeeSearchItems(localQuery, latestIndex)
|
||||
|
||||
items =
|
||||
latestIndex === -1
|
||||
? [...multipleEmployeeSearchItems, ...r.items]
|
||||
: [...r.items.slice(0, latestIndex + 1), ...multipleEmployeeSearchItems, ...r.items.slice(latestIndex + 1)]
|
||||
}
|
||||
})
|
||||
$: void updateItems(query)
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { Markup } from '@hcengineering/core'
|
||||
import { Class, Doc, Markup, Ref } from '@hcengineering/core'
|
||||
import { Asset, IntlString } from '@hcengineering/platform'
|
||||
import { EmptyMarkup, isEmptyMarkup } from '@hcengineering/text'
|
||||
import textEditor, { RefAction, TextEditorHandler } from '@hcengineering/text-editor'
|
||||
@@ -61,6 +61,7 @@
|
||||
export let canEmbedImages = true
|
||||
export let onPaste: ((view: EditorView, event: ClipboardEvent) => boolean) | undefined = undefined
|
||||
export let onCancel: (() => void) | undefined = undefined
|
||||
export let docClass: Ref<Class<Doc>> | undefined = undefined
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
const buttonSize = 'medium'
|
||||
@@ -142,6 +143,8 @@
|
||||
}
|
||||
const completionPlugin = ReferenceExtension.configure({
|
||||
...referenceConfig,
|
||||
docClass,
|
||||
multipleMentions: true,
|
||||
showDoc (event: MouseEvent, _id: string, _class: string) {
|
||||
dispatch('open-document', { event, _id, _class })
|
||||
}
|
||||
|
||||
@@ -32,6 +32,8 @@ import workbench, { type Application } from '@hcengineering/workbench'
|
||||
|
||||
export interface ReferenceExtensionOptions extends ReferenceOptions {
|
||||
suggestion: Omit<SuggestionOptions, 'editor'>
|
||||
docClass?: Ref<Class<Doc>>
|
||||
multipleMentions?: boolean
|
||||
showDoc?: (event: MouseEvent, _id: string, _class: string) => void
|
||||
}
|
||||
|
||||
@@ -96,10 +98,12 @@ export const ReferenceExtension = ReferenceNode.extend<ReferenceExtensionOptions
|
||||
this.options.HTMLAttributes,
|
||||
HTMLAttributes
|
||||
)
|
||||
const withoutDoc = [contact.mention.Everyone, contact.mention.Here].includes(node.attrs.id)
|
||||
const id = node.attrs.id
|
||||
const objectclass: Ref<Class<Doc>> = node.attrs.objectclass
|
||||
|
||||
root.addEventListener('click', (event) => {
|
||||
if (withoutDoc) return
|
||||
if (event.button !== 0) return
|
||||
if (broken) {
|
||||
showPopup(MessageBox, {
|
||||
@@ -164,7 +168,7 @@ export const ReferenceExtension = ReferenceNode.extend<ReferenceExtensionOptions
|
||||
const titleSpan = root.appendChild(document.createElement('span'))
|
||||
renderLabel({ id, objectclass, label: node.attrs.label })
|
||||
|
||||
if (id !== undefined && objectclass !== undefined) {
|
||||
if (id !== undefined && objectclass !== undefined && !withoutDoc) {
|
||||
query.query(objectclass, { _id: id }, async (result) => {
|
||||
const obj = result[0]
|
||||
broken = obj === undefined
|
||||
@@ -180,6 +184,8 @@ export const ReferenceExtension = ReferenceNode.extend<ReferenceExtensionOptions
|
||||
renderLabel({ id, objectclass, label })
|
||||
}
|
||||
})
|
||||
} else if (withoutDoc) {
|
||||
query.unsubscribe()
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -228,6 +234,8 @@ export const ReferenceExtension = ReferenceNode.extend<ReferenceExtensionOptions
|
||||
return [
|
||||
Suggestion({
|
||||
editor: this.editor,
|
||||
docClass: this.options.docClass,
|
||||
multipleMentions: this.options.multipleMentions,
|
||||
...this.options.suggestion
|
||||
}),
|
||||
// ReferenceClickHandler(this.options),
|
||||
|
||||
@@ -2,8 +2,8 @@ import { type Editor, type Range, escapeForRegEx } from '@tiptap/core'
|
||||
import { type EditorState, Plugin, PluginKey, type Transaction } from '@tiptap/pm/state'
|
||||
import { ReplaceStep } from '@tiptap/pm/transform'
|
||||
import { Decoration, DecorationSet, type EditorView } from '@tiptap/pm/view'
|
||||
|
||||
import { type ResolvedPos } from '@tiptap/pm/model'
|
||||
import { type Class, type Doc, type Ref } from '@hcengineering/core'
|
||||
|
||||
export interface Trigger {
|
||||
char: string
|
||||
@@ -129,6 +129,8 @@ export interface SuggestionOptions<I = any> {
|
||||
startOfLine?: boolean
|
||||
decorationTag?: string
|
||||
decorationClass?: string
|
||||
multipleMentions?: boolean
|
||||
docClass?: Ref<Class<Doc>>
|
||||
command?: (props: { editor: Editor, range: Range, props: I }) => void
|
||||
items?: (props: { query: string, editor: Editor }) => I[] | Promise<I[]>
|
||||
render?: () => {
|
||||
@@ -147,6 +149,8 @@ export interface SuggestionProps<I = any> {
|
||||
range: Range
|
||||
query: string
|
||||
text: string
|
||||
multipleMentions: boolean
|
||||
docClass?: Ref<Class<Doc>>
|
||||
items: I[]
|
||||
command: (props: I) => void
|
||||
decorationNode: Element | null
|
||||
@@ -172,6 +176,8 @@ export default function Suggestion<I = any> ({
|
||||
startOfLine = false,
|
||||
decorationTag = 'span',
|
||||
decorationClass = 'suggestion',
|
||||
multipleMentions = false,
|
||||
docClass,
|
||||
command = () => null,
|
||||
items = () => [],
|
||||
render = () => ({}),
|
||||
@@ -226,7 +232,9 @@ export default function Suggestion<I = any> ({
|
||||
range: state.range,
|
||||
query: state.query,
|
||||
text: state.text,
|
||||
docClass,
|
||||
items: [],
|
||||
multipleMentions,
|
||||
command: (commandProps) => {
|
||||
command({
|
||||
editor,
|
||||
|
||||
Reference in New Issue
Block a user