diff --git a/models/card/src/index.ts b/models/card/src/index.ts index 4da3f66d1e..9c4e6b3d8f 100644 --- a/models/card/src/index.ts +++ b/models/card/src/index.ts @@ -161,7 +161,7 @@ export function createSystemType ( attachTo: type, descriptor: view.viewlet.List, viewOptions: { - groupBy: ['_class', 'createdBy', 'modifiedBy'], + groupBy: ['_class', 'createdBy', 'modifiedBy', 'parent'], orderBy: [ ['modifiedOn', SortingOrder.Descending], ['rank', SortingOrder.Ascending] @@ -281,7 +281,7 @@ export function createModel (builder: Builder): void { attachTo: card.class.Card, descriptor: view.viewlet.List, viewOptions: { - groupBy: ['_class', 'createdBy', 'modifiedBy'], + groupBy: ['_class', 'createdBy', 'modifiedBy', 'parent'], orderBy: [ ['modifiedOn', SortingOrder.Descending], ['rank', SortingOrder.Ascending] @@ -304,7 +304,7 @@ export function createModel (builder: Builder): void { descriptor: view.viewlet.List, variant: 'child', viewOptions: { - groupBy: ['_class', 'createdBy', 'modifiedBy'], + groupBy: ['_class', 'createdBy', 'modifiedBy', 'parent'], orderBy: [ ['modifiedOn', SortingOrder.Descending], ['rank', SortingOrder.Ascending] diff --git a/models/view/src/plugin.ts b/models/view/src/plugin.ts index 84291c2e3b..7cb168668a 100644 --- a/models/view/src/plugin.ts +++ b/models/view/src/plugin.ts @@ -72,7 +72,6 @@ export default mergeIds(viewId, view, { DatePresenter: '' as AnyComponent, DateTimePresenter: '' as AnyComponent, TableBrowser: '' as AnyComponent, - RolePresenter: '' as AnyComponent, YoutubePresenter: '' as AnyComponent, GithubPresenter: '' as AnyComponent, ClassPresenter: '' as AnyComponent, diff --git a/packages/ui-next/lang/cs.json b/packages/ui-next/lang/cs.json index ae7d1e7703..accb0b306e 100644 --- a/packages/ui-next/lang/cs.json +++ b/packages/ui-next/lang/cs.json @@ -1,6 +1,7 @@ { "string": { "Added": "Přidáno", + "All": "Vše ({count})", "Attach": "Připojit", "Cancel": "Zrušit", "Edit": "Upravit", diff --git a/packages/ui-next/lang/de.json b/packages/ui-next/lang/de.json index 8f1a616815..dbcdec1df1 100644 --- a/packages/ui-next/lang/de.json +++ b/packages/ui-next/lang/de.json @@ -1,6 +1,7 @@ { "string": { "Added": "Hinzugefügt", + "All": "Alle ({count})", "Attach": "Anhängen", "Cancel": "Abbrechen", "Edit": "Bearbeiten", diff --git a/packages/ui-next/lang/en.json b/packages/ui-next/lang/en.json index ada9377dbd..912a73febc 100644 --- a/packages/ui-next/lang/en.json +++ b/packages/ui-next/lang/en.json @@ -1,6 +1,7 @@ { "string": { "Added": "Added", + "All": "All ({count})", "Attach": "Attach", "Cancel": "Cancel", "Edit": "Edit", diff --git a/packages/ui-next/lang/es.json b/packages/ui-next/lang/es.json index 154602a5e9..e56675a57e 100644 --- a/packages/ui-next/lang/es.json +++ b/packages/ui-next/lang/es.json @@ -1,6 +1,7 @@ { "string": { "Added": "Añadido", + "All": "Todo ({count})", "Attach": "Adjuntar", "Cancel": "Cancelar", "Edit": "Editar", diff --git a/packages/ui-next/lang/fr.json b/packages/ui-next/lang/fr.json index 05a86af885..3beb1d5e81 100644 --- a/packages/ui-next/lang/fr.json +++ b/packages/ui-next/lang/fr.json @@ -1,6 +1,7 @@ { "string": { "Added": "Ajouté", + "All": "Tout ({count})", "Attach": "Joindre", "Cancel": "Annuler", "Edit": "Modifier", diff --git a/packages/ui-next/lang/it.json b/packages/ui-next/lang/it.json index d544e1261c..ae5dc9a98d 100644 --- a/packages/ui-next/lang/it.json +++ b/packages/ui-next/lang/it.json @@ -1,6 +1,7 @@ { "string": { "Added": "Aggiunto" , + "All": "Tutto ({count})", "Attach": "Allega", "Cancel": "Annulla", "Edit": "Modifica", diff --git a/packages/ui-next/lang/pt.json b/packages/ui-next/lang/pt.json index 8990eadd4d..8cb43053d6 100644 --- a/packages/ui-next/lang/pt.json +++ b/packages/ui-next/lang/pt.json @@ -1,6 +1,7 @@ { "string": { "Added": "Adicionado", + "All": "Todos ({count})", "Attach": "Anexar", "Cancel": "Cancelar", "Edit": "Editar", diff --git a/packages/ui-next/lang/ru.json b/packages/ui-next/lang/ru.json index 09e63991a9..c985eb2eb3 100644 --- a/packages/ui-next/lang/ru.json +++ b/packages/ui-next/lang/ru.json @@ -1,6 +1,7 @@ { "string": { "Added": "Добавлено", + "All": "Всего ({count})", "Attach": "Прикрепить", "Cancel": "Отмена", "Edit": "Редактировать", diff --git a/packages/ui-next/lang/zh.json b/packages/ui-next/lang/zh.json index b54eafefa1..a1d7831753 100644 --- a/packages/ui-next/lang/zh.json +++ b/packages/ui-next/lang/zh.json @@ -1,6 +1,7 @@ { "string": { "Added": "已添加", + "All": "全部 ({count})", "Attach": "附加", "Cancel": "取消", "Edit": "编辑", diff --git a/packages/ui-next/src/components/NavigationList.svelte b/packages/ui-next/src/components/NavigationList.svelte index 2b299b250a..be8e899dc7 100644 --- a/packages/ui-next/src/components/NavigationList.svelte +++ b/packages/ui-next/src/components/NavigationList.svelte @@ -14,31 +14,53 @@ --> diff --git a/packages/ui-next/src/components/Section.svelte b/packages/ui-next/src/components/Section.svelte index 87965370a7..b743ae52cd 100644 --- a/packages/ui-next/src/components/Section.svelte +++ b/packages/ui-next/src/components/Section.svelte @@ -26,12 +26,15 @@ export let title: IntlString export let withHeaderDivider = false export let expanded = true + export let selected = false const dispatch = createEventDispatcher() let _expanded = expanded - function toggleExpanded (): void { + function toggleExpanded (event: MouseEvent): void { + event.stopPropagation() + event.preventDefault() _expanded = !_expanded dispatch('toggle', id) } @@ -40,17 +43,19 @@
-
-
-
-
+
+
{#if _expanded} {:else} {/if}
+ +
+
+ {#if withHeaderDivider} {/if} @@ -75,11 +80,17 @@ .section__header { display: flex; width: 100%; - height: 1.5rem; + min-height: 1.5rem; align-items: center; gap: 0.375rem; flex-shrink: 0; cursor: pointer; + border-radius: 0.5rem; + padding: 0.5rem; + + &.selected { + background: var(--next-surface-color-fg-inv); + } } .section__title { diff --git a/packages/ui-next/src/plugin.ts b/packages/ui-next/src/plugin.ts index 18aafb8f81..0abb117d95 100644 --- a/packages/ui-next/src/plugin.ts +++ b/packages/ui-next/src/plugin.ts @@ -20,6 +20,7 @@ export const uiNextId = 'ui-next' as Plugin export const uiNext = plugin(uiNextId, { string: { Added: '' as IntlString, + All: '' as IntlString, Attach: '' as IntlString, Cancel: '' as IntlString, Edit: '' as IntlString, diff --git a/packages/ui-next/src/types.ts b/packages/ui-next/src/types.ts index 1cd0bbf66a..fa8e654739 100644 --- a/packages/ui-next/src/types.ts +++ b/packages/ui-next/src/types.ts @@ -22,6 +22,7 @@ export interface NavigationSection { id: string title: IntlString expanded: boolean + total: number items: NavigationSectionItem[] } diff --git a/plugins/chat-resources/src/components/ChatApplication.svelte b/plugins/chat-resources/src/components/ChatApplication.svelte index 382e1c9d7f..2be6961986 100644 --- a/plugins/chat-resources/src/components/ChatApplication.svelte +++ b/plugins/chat-resources/src/components/ChatApplication.svelte @@ -14,7 +14,7 @@ -->
- + + dispatch('selectType', ev.detail)} + on:all={handleAll} + />
@@ -72,6 +136,6 @@ flex-direction: column; width: 100%; padding: 0 8px; - margin-top: 3rem; + margin-top: 1rem; } diff --git a/plugins/chat-resources/src/components/ChatNavigationCategoryList.svelte b/plugins/chat-resources/src/components/ChatNavigationCategoryList.svelte new file mode 100644 index 0000000000..dd7a505ba8 --- /dev/null +++ b/plugins/chat-resources/src/components/ChatNavigationCategoryList.svelte @@ -0,0 +1,98 @@ + + + + +{#if clazz} + +{/if} diff --git a/plugins/chat-resources/src/components/ChatWidget.svelte b/plugins/chat-resources/src/components/ChatWidget.svelte index b6f9410328..bf91e6eca6 100644 --- a/plugins/chat-resources/src/components/ChatWidget.svelte +++ b/plugins/chat-resources/src/components/ChatWidget.svelte @@ -101,22 +101,23 @@ async function handleSubmit (markdown: string, files: UploadedFile[]): Promise { if (message === undefined || data === undefined) return const { card } = data - if (card === undefined) return + if (card === undefined || parentCard === undefined) return let threadId: CardID | undefined = message.thread?.thread if (threadId == null) { const markup = jsonToMarkup(markdownToMarkup(message.content)) - const title = markupToText(markup).trim() + const messageText = markupToText(markup).trim() const lastOne = await client.findOne(cardPlugin.class.Card, {}, { sort: { rank: SortingOrder.Descending } }) + const titleFromMessage = `${messageText.slice(0, 100)}${messageText.length > 100 ? '...' : ''}` + const title = titleFromMessage.length > 0 ? titleFromMessage : `Thread from ${parentCard.title}` const data = fillDefaults( hierarchy, { - title: `Thread: ${title.slice(0, 100)}${title.length > 100 ? '...' : ''}`, + title, rank: makeRank(lastOne?.rank, undefined), content: '' as MarkupBlobRef, - parentInfo: [], - blobs: {} + parent: parentCard._id }, chat.masterTag.Thread ) diff --git a/plugins/chat-resources/src/location.ts b/plugins/chat-resources/src/location.ts index 6da63799bb..57a77071a8 100644 --- a/plugins/chat-resources/src/location.ts +++ b/plugins/chat-resources/src/location.ts @@ -13,8 +13,8 @@ // limitations under the License. // -import { type Card } from '@hcengineering/card' -import type { Ref } from '@hcengineering/core' +import { type Card, type MasterTag } from '@hcengineering/card' +import type { Doc, Ref } from '@hcengineering/core' import { navigate, type Location, getCurrentResolvedLocation } from '@hcengineering/ui' import { chatId } from '@hcengineering/chat' import { getClient } from '@hcengineering/presentation' @@ -25,22 +25,51 @@ import { openWidget } from '@hcengineering/workbench-resources' import chat from './plugin' import { type ChatWidgetData } from './types' -// Url: /chat/{cardId}/{threadId}?message={messageId} +export function decodeURI (value: string): ['type', Ref] | ['card', Ref] { + return decodeURIComponent(value).split('|') as any +} + +export function encodeURI (type: 'type' | 'card', ref: Ref): string { + return [type, ref].join('|') +} export function getCardIdFromLocation (loc: Location): Ref | undefined { if (loc.path[2] !== chatId) { return undefined } - return loc.path[3] as Ref + const [type, ref] = decodeURI(loc.path[3]) + if (type !== 'card') { + return undefined + } + return ref +} + +export function getTypeIdFromLocation (loc: Location): Ref | undefined { + if (loc.path[2] !== chatId) { + return undefined + } + const [type, ref] = decodeURI(loc.path[3]) + if (type !== 'type') { + return undefined + } + return ref } export function navigateToCard (_id: Ref): void { const loc = getCurrentResolvedLocation() loc.path[2] = chatId - loc.path[3] = _id - loc.path[4] = '' - loc.path.length = 4 + loc.path[3] = encodeURI('card', _id) + delete loc.query?.message + + navigate(loc) +} + +export function navigateToType (_id: Ref): void { + const loc = getCurrentResolvedLocation() + + loc.path[2] = chatId + loc.path[3] = encodeURI('type', _id) delete loc.query?.message navigate(loc) diff --git a/plugins/chat-resources/src/navigator.ts b/plugins/chat-resources/src/navigator.ts index f184062f0e..90c16c5d09 100644 --- a/plugins/chat-resources/src/navigator.ts +++ b/plugins/chat-resources/src/navigator.ts @@ -15,7 +15,7 @@ import { type Card, type MasterTag } from '@hcengineering/card' import { type NavigationSection } from '@hcengineering/ui-next' -import { groupByArray, type Hierarchy, type Ref } from '@hcengineering/core' +import { type Hierarchy, type Ref } from '@hcengineering/core' import chat from '@hcengineering/chat' import { translate } from '@hcengineering/platform' import { getClient } from '@hcengineering/presentation' @@ -59,28 +59,30 @@ export function toggleSection (id: string): void { } export async function cardsToChatSections ( - cards: Card[], + cardsByType: Map, { cards: Card[], total: number }>, contexts: NotificationContext[], state: NavigatorState ): Promise { const client = getClient() const hierarchy = client.getHierarchy() - const { threads, channels, other } = splitCards(cards, hierarchy) - const cardByClass = groupByArray(other, (it) => it._class) + const threads = cardsByType.get(chat.masterTag.Thread) + const channels = cardsByType.get(chat.masterTag.Channel) const result: NavigationSection[] = [] - if (threads.length > 0) { - result.push(getSection(chat.masterTag.Thread, threads, contexts, state, hierarchy)) + if (threads != null && threads.cards.length > 0) { + result.push(getSection(chat.masterTag.Thread, threads.cards, threads.total, contexts, state, hierarchy)) } - if (channels.length > 0) { - result.push(getSection(chat.masterTag.Channel, channels, contexts, state, hierarchy)) + if (channels != null && channels.cards.length > 0) { + result.push(getSection(chat.masterTag.Channel, channels.cards, channels.total, contexts, state, hierarchy)) } const cardSessions: Array<[string, NavigationSection]> = [] - for (const [_class, cards] of cardByClass.entries()) { - const section = getSection(_class, cards, contexts, state, hierarchy) + for (const [type, cards] of cardsByType.entries()) { + if (type === chat.masterTag.Thread || type === chat.masterTag.Channel) continue + if (cards.cards.length === 0) continue + const section = getSection(type, cards.cards, cards.total, contexts, state, hierarchy) const label = await translate(section.title, {}) cardSessions.push([label, section]) } @@ -95,6 +97,7 @@ export async function cardsToChatSections ( function getSection ( _class: Ref, cards: Card[], + total: number, contexts: NotificationContext[], state: NavigatorState, hierarchy: Hierarchy @@ -105,43 +108,15 @@ function getSection ( id: _class, title: clazz.pluralLabel ?? clazz.label, expanded: !state.collapsedSections.includes(_class), - items: cards - .map((card) => { - const context = contexts.find((it) => it.card === card._id) - return { - id: card._id, - label: card.title, - icon: clazz.icon ?? chat.icon.Thread, - notificationsCount: context?.notifications?.length ?? 0 - } - }) - .sort((c1, c2) => c1.label.toLowerCase().localeCompare(c2.label.toLowerCase())) + total, + items: cards.map((card) => { + const context = contexts.find((it) => it.card === card._id) + return { + id: card._id, + label: card.title, + icon: clazz.icon ?? chat.icon.Thread, + notificationsCount: context?.notifications?.length ?? 0 + } + }) } } - -function splitCards ( - cards: Card[], - hierarchy: Hierarchy -): { - threads: Card[] - channels: Card[] - other: Card[] - } { - return cards.reduce<{ - threads: Card[] - channels: Card[] - other: Card[] - }>( - (acc, it) => { - if (hierarchy.isDerived(it._class, chat.masterTag.Thread)) { - acc.threads.push(it) - } else if (hierarchy.isDerived(it._class, chat.masterTag.Channel)) { - acc.channels.push(it) - } else { - acc.other.push(it) - } - return acc - }, - { threads: [], channels: [], other: [] } - ) -} diff --git a/plugins/view-resources/src/components/ViewletSelector.svelte b/plugins/view-resources/src/components/ViewletSelector.svelte index 36e99d6678..e057ee1817 100644 --- a/plugins/view-resources/src/components/ViewletSelector.svelte +++ b/plugins/view-resources/src/components/ViewletSelector.svelte @@ -2,7 +2,7 @@ import { createEventDispatcher, onDestroy } from 'svelte' import { activeViewlet, makeViewletKey, setActiveViewletId } from '../utils' import { resolvedLocationStore, Switcher } from '@hcengineering/ui' - import view, { Viewlet, ViewletPreference } from '@hcengineering/view' + import view, { Viewlet, ViewletDescriptor, ViewletPreference } from '@hcengineering/view' import core, { DocumentQuery, Ref, WithLookup } from '@hcengineering/core' import { deepEqual } from 'fast-equals' import { createQuery } from '@hcengineering/presentation' @@ -14,6 +14,7 @@ export let loading = true export let hidden = false export let ignoreFragment = false + export let defaultViewletDescriptor: Ref | undefined = undefined const query = createQuery() @@ -47,9 +48,12 @@ viewlets: Array>, activeViewlet: Record | null>, key: string - ) { + ): void { if (viewlets == null || viewlets.length === 0) return - const newViewlet = viewlets.find((viewlet) => viewlet?._id === activeViewlet[key]) ?? viewlets[0] + const newViewlet = + viewlets.find((viewlet) => viewlet?._id === activeViewlet[key]) ?? + viewlets.find((viewlet) => viewlet.descriptor === defaultViewletDescriptor) ?? + viewlets[0] if (viewlet?._id !== newViewlet?._id || !deepEqual(viewlet.config, newViewlet.config)) { viewlet = newViewlet setActiveViewletId(newViewlet._id) diff --git a/plugins/view-resources/src/components/ViewletSetting.svelte b/plugins/view-resources/src/components/ViewletSetting.svelte index d1efc5be83..a1e51c34b9 100644 --- a/plugins/view-resources/src/components/ViewletSetting.svelte +++ b/plugins/view-resources/src/components/ViewletSetting.svelte @@ -26,6 +26,7 @@ import ViewletClassSettings from './ViewletClassSettings.svelte' export let viewlet: Viewlet + export let defaultConfig: (BuildModelKey | string)[] | undefined = undefined const dispatch = createEventDispatcher() @@ -98,11 +99,13 @@ } function getBaseConfig (viewlet: Viewlet): Config[] { - const lookup = buildConfigLookup(hierarchy, viewlet.attachTo, viewlet.config, viewlet.options?.lookup) + const config = defaultConfig ?? viewlet.config + const lookup = buildConfigLookup(hierarchy, viewlet.attachTo, config, viewlet.options?.lookup) const result: Config[] = [] const clazz = hierarchy.getClass(viewlet.attachTo) let wasOptional = false - for (const param of viewlet.config) { + + for (const param of config) { if (typeof param === 'string') { if (viewlet.configOptions?.hiddenKeys?.includes(param)) continue if (param.length === 0) { diff --git a/plugins/view-resources/src/components/ViewletSettingButton.svelte b/plugins/view-resources/src/components/ViewletSettingButton.svelte index 85c4e2baf6..538121eb02 100644 --- a/plugins/view-resources/src/components/ViewletSettingButton.svelte +++ b/plugins/view-resources/src/components/ViewletSettingButton.svelte @@ -14,7 +14,7 @@ --> diff --git a/plugins/view-resources/src/viewOptions.ts b/plugins/view-resources/src/viewOptions.ts index dbe8baa170..db1ee68c59 100644 --- a/plugins/view-resources/src/viewOptions.ts +++ b/plugins/view-resources/src/viewOptions.ts @@ -19,7 +19,6 @@ import { type ToggleViewOption, type ViewOptionModel, type ViewOptions, - type ViewOptionsModel, type Viewlet, type ViewletDescriptor } from '@hcengineering/view' @@ -84,17 +83,6 @@ function _getViewOptions (viewlet: Viewlet, viewOptionStore: Map, @@ -105,7 +93,7 @@ export function getViewOptions ( } const res = _getViewOptions(viewlet, viewOptionStore) if (res !== null) return res - return viewlet.viewOptions != null ? getDefaults(viewlet.viewOptions) : defaults + return defaults } export function migrateViewOpttions (): void { diff --git a/plugins/view/src/index.ts b/plugins/view/src/index.ts index 3374a8d366..571f7be5ca 100644 --- a/plugins/view/src/index.ts +++ b/plugins/view/src/index.ts @@ -174,7 +174,8 @@ const view = plugin(viewId, { ObjectMention: '' as AnyComponent, SearchSelector: '' as AnyComponent, FoldersBrowser: '' as AnyComponent, - PersonIdPresenter: '' as AnyComponent + PersonIdPresenter: '' as AnyComponent, + RolePresenter: '' as AnyComponent }, ids: { IconWithEmoji: '' as Asset diff --git a/plugins/workbench-resources/src/components/SpecialView.svelte b/plugins/workbench-resources/src/components/SpecialView.svelte index 3cd565b8d5..a9ebb6badb 100644 --- a/plugins/workbench-resources/src/components/SpecialView.svelte +++ b/plugins/workbench-resources/src/components/SpecialView.svelte @@ -29,7 +29,7 @@ SearchInput, showPopup } from '@hcengineering/ui' - import { Viewlet, ViewletDescriptor, ViewletPreference, ViewOptions } from '@hcengineering/view' + import { Viewlet, ViewletDescriptor, ViewletPreference, ViewOptions, BuildModelKey } from '@hcengineering/view' import { FilterBar, FilterButton, @@ -58,6 +58,9 @@ export let queryBuilder: Resource<() => Promise>> | undefined = undefined export let actionConfig: Record = {} export let actionVisible: boolean = false + export let defaultViewletDescriptor: Ref | undefined = undefined + export let defaultViewOptions: ViewOptions | undefined = undefined + export let defaultConfig: (BuildModelKey | string)[] | undefined = undefined const client = getClient() const hierarchy = client.getHierarchy() @@ -130,6 +133,7 @@ bind:viewlet bind:preference bind:viewlets + {defaultViewletDescriptor} ignoreFragment viewletQuery={{ attachTo: _class, @@ -137,7 +141,7 @@ ...(descriptors !== undefined ? { descriptor: { $in: descriptors } } : {}) }} /> - + @@ -198,7 +202,7 @@ _class, space, options, - config: preference?.config ?? viewlet.config, + config: preference?.config ?? defaultConfig ?? viewlet.config, viewlet, viewOptions, viewOptionsConfig: viewlet.viewOptions?.other, @@ -218,7 +222,7 @@ _class, space, options, - config: preference?.config ?? viewlet.config, + config: preference?.config ?? defaultConfig ?? viewlet.config, viewlet, viewOptions, viewOptionsConfig: viewlet.viewOptions?.other,