diff --git a/models/card/src/index.ts b/models/card/src/index.ts
index b6c9380364..2a83d69b7e 100644
--- a/models/card/src/index.ts
+++ b/models/card/src/index.ts
@@ -175,9 +175,11 @@ const listConfig: (BuildModelKey | string)[] = [
{ key: '', displayProps: { grow: true } },
{
key: '',
- presenter: view.component.RolePresenter,
+ presenter: card.component.CardTagsColored,
label: card.string.Tags,
- props: { fullSize: true },
+ props: {
+ showType: false
+ },
displayProps: { key: 'tags', fixed: 'right' }
},
{
@@ -230,10 +232,10 @@ const favoritesViewletConfig: (BuildModelKey | string)[] = [
},
key: '$lookup.attachedTo',
label: card.string.Tags,
- presenter: view.component.RolePresenter,
props: {
- fullSize: true
- }
+ showType: false
+ },
+ presenter: card.component.CardTagsColored
},
{
key: '$lookup.attachedTo',
@@ -285,7 +287,14 @@ export function createSystemType (
config: [
{ key: '', props: { shrink: true } },
'_class',
- { key: '', presenter: view.component.RolePresenter, label: card.string.Tags, props: { fullSize: true } },
+ {
+ key: '',
+ presenter: card.component.CardTagsColored,
+ label: card.string.Tags,
+ props: {
+ showType: false
+ }
+ },
{
key: '',
presenter: card.component.LabelsPresenter,
@@ -519,7 +528,14 @@ export function createModel (builder: Builder): void {
config: [
'',
'_class',
- { key: '', presenter: view.component.RolePresenter, label: card.string.Tags, props: { fullSize: true } },
+ {
+ key: '',
+ presenter: card.component.CardTagsColored,
+ label: card.string.Tags,
+ props: {
+ showType: false
+ }
+ },
'modifiedOn'
]
},
diff --git a/plugins/card-resources/src/components/CardTagsColored.svelte b/plugins/card-resources/src/components/CardTagsColored.svelte
index ccaf924420..b78c0f342a 100644
--- a/plugins/card-resources/src/components/CardTagsColored.svelte
+++ b/plugins/card-resources/src/components/CardTagsColored.svelte
@@ -18,6 +18,8 @@
import CardTagColored from './CardTagColored.svelte'
export let value: Card
+ export let showTags: boolean = true
+ export let showType: boolean = true
const client = getClient()
const hierarchy = client.getHierarchy()
@@ -37,9 +39,13 @@
@@ -316,4 +360,26 @@
align-items: flex-start;
align-self: stretch;
}
+
+ .down-button {
+ position: absolute;
+ width: 100%;
+ display: flex;
+ justify-content: center;
+ bottom: 0.5rem;
+ animation: 0.5s fadeIn;
+ animation-fill-mode: forwards;
+ visibility: hidden;
+ left: 0;
+ right: 0;
+ }
+
+ @keyframes fadeIn {
+ 99% {
+ visibility: hidden;
+ }
+ 100% {
+ visibility: visible;
+ }
+ }
diff --git a/plugins/chat-resources/src/components/ChatNavigationCategoryList.svelte b/plugins/chat-resources/src/components/ChatNavigationCategoryList.svelte
index bae8be1dfc..bd87f4f72f 100644
--- a/plugins/chat-resources/src/components/ChatNavigationCategoryList.svelte
+++ b/plugins/chat-resources/src/components/ChatNavigationCategoryList.svelte
@@ -54,9 +54,9 @@
},
key: '',
label: card.string.Tags,
- presenter: view.component.RolePresenter,
+ presenter: card.component.CardTagsColored,
props: {
- fullSize: true
+ showType: false
}
},
{
diff --git a/plugins/communication-resources/src/components/CardMessagesSection.svelte b/plugins/communication-resources/src/components/CardMessagesSection.svelte
index e94f5cbdda..1bfa5b90ab 100644
--- a/plugins/communication-resources/src/components/CardMessagesSection.svelte
+++ b/plugins/communication-resources/src/components/CardMessagesSection.svelte
@@ -33,6 +33,8 @@
const dispatch = createEventDispatcher()
+ let list: MessagesList | undefined = undefined
+
let position: MessagesNavigationAnchors =
navigation === MessagesNavigationAnchors.LatestMessages
? MessagesNavigationAnchors.LatestMessages
@@ -49,6 +51,16 @@
position = id
}
+ export function scrollDown (): void {
+ shouldScrollToStart = false
+ position = MessagesNavigationAnchors.LatestMessages
+ list?.scrollDown()
+ }
+
+ export function canScrollDown (): boolean {
+ return list?.canScrollDown() ?? false
+ }
+
onMount(() => {
if (active && navigation === MessagesNavigationAnchors.LatestMessages) {
position = MessagesNavigationAnchors.LatestMessages
@@ -60,6 +72,7 @@
{#if scrollDiv != null && isContextLoaded && contentDiv != null}
initialLastView
let queryDef = getBaseQuery()
+ export function scrollDown (): void {
+ shouldScrollToEnd = true
+ position = MessagesNavigationAnchors.LatestMessages
+
+ reinit(position, true)
+ }
+
+ export function canScrollDown (): boolean {
+ return window !== undefined && window.hasNextPage()
+ }
$: if (
(context?.lastView?.getTime() ?? 0) >= (context?.lastUpdate?.getTime() ?? 0) &&
(notifications?.length ?? 0) > 0 &&
@@ -156,8 +167,8 @@
}
}
- function reinit (position: MessagesNavigationAnchors): void {
- if (prevPosition === position) {
+ function reinit (position: MessagesNavigationAnchors, force = false): void {
+ if (prevPosition === position && !force) {
return
}
prevPosition = position
@@ -191,7 +202,10 @@
limit
}
}
- const order = unread ? SortingOrder.Ascending : SortingOrder.Descending
+ initialLastView = context?.lastView
+ initialLastUpdate = context?.lastUpdate
+ const unread = initialLastView != null && initialLastUpdate != null && initialLastUpdate > initialLastView
+ const order = unread && !shouldScrollToEnd ? SortingOrder.Ascending : SortingOrder.Descending
return {
card: card._id,
replies: true,
@@ -200,7 +214,7 @@
links: true,
order,
limit,
- from: unread && initialLastView != null ? initialLastView : undefined
+ from: unread && !shouldScrollToEnd && initialLastView != null ? initialLastView : undefined
}
}
@@ -231,12 +245,12 @@
return bottomOffset <= 200
}
- function loadMore (): void {
+ function loadMore (direction: 'up' | 'down'): void {
if (window === undefined || !isScrollInitialized) return
- if (shouldLoadPrevPage() && window.hasPrevPage()) {
+ if (shouldLoadPrevPage() && window.hasPrevPage() && direction === 'up') {
void loadPrevPage()
- } else if (shouldLoadNextPage() && window.hasNextPage()) {
+ } else if (shouldLoadNextPage() && window.hasNextPage() && direction === 'down') {
void loadNextPage()
}
}
@@ -312,13 +326,19 @@
}
}
let rafId: any | null = null
+ let lastScrollTop: number = 0
+
function handleScroll (): void {
if (rafId !== null) return
rafId = requestAnimationFrame(() => {
+ const top = scrollDiv.scrollTop
+ const direction = top > lastScrollTop ? 'down' : 'up'
+
+ lastScrollTop = top
bottomOffset = getBottomOffset()
topOffset = getTopOffset()
updateShouldScrollToNew()
- loadMore()
+ loadMore(direction)
checkPositionOnScroll()
void readAll()
rafId = null
@@ -531,11 +551,15 @@
const separatorIndex =
initialLastView !== undefined
- ? messages.findIndex(({ created, creator }) => !me.socialIds.includes(creator) && created > initialLastView)
+ ? messages.findIndex(
+ ({ created, creator }) =>
+ initialLastView != null && !me.socialIds.includes(creator) && created > initialLastView
+ )
: -1
- if (separatorIndex === -1) {
+ if (separatorIndex === -1 || shouldScrollToEnd) {
await tick() // Wait for the DOM to update
+ shouldScrollToEnd = false
scrollToBottom(true)
shouldScrollToNew = true
atBottom = true
@@ -551,7 +575,9 @@
if (separatorDiv != null) {
await tick() // Wait for the DOM to update
scrollToWithOffset(scrollDiv, separatorDiv, 80)
- isScrollInitialized = true
+ setTimeout(() => {
+ isScrollInitialized = true
+ }, 10)
updateShouldScrollToNew()
bottomOffset = getBottomOffset()
topOffset = getTopOffset()
diff --git a/plugins/inbox-resources/src/components/InboxApplication.svelte b/plugins/inbox-resources/src/components/InboxApplication.svelte
index a2da278f49..0d99e7c98d 100644
--- a/plugins/inbox-resources/src/components/InboxApplication.svelte
+++ b/plugins/inbox-resources/src/components/InboxApplication.svelte
@@ -23,25 +23,20 @@
Location,
restoreLocation,
Component,
- Label,
- ModernButton,
- IconDelete,
closePanel,
getCurrentLocation
} from '@hcengineering/ui'
import { onDestroy } from 'svelte'
- import { getClient, getCommunicationClient } from '@hcengineering/presentation'
+ import { getClient } from '@hcengineering/presentation'
import { inboxId } from '@hcengineering/inbox'
import view from '@hcengineering/view'
import { NotificationContext } from '@hcengineering/communication-types'
- import { SortingOrder } from '@hcengineering/core'
import InboxNavigation from './InboxNavigation.svelte'
import { getCardIdFromLocation, navigateToCard } from '../location'
- import inbox from '../plugin'
+ import InboxHeader from './InboxHeader.svelte'
const client = getClient()
- const communicationClient = getCommunicationClient()
let replacedPanelElement: HTMLElement
let card: Card | undefined = undefined
@@ -95,20 +90,6 @@
$: $deviceInfo.replacedPanel = replacedPanelElement
onDestroy(() => ($deviceInfo.replacedPanel = undefined))
-
- let clearing = false
- async function clearInbox (): Promise {
- if (clearing) return
- try {
- clearing = true
- const contexts = await communicationClient.findNotificationContexts({ order: SortingOrder.Ascending })
- await Promise.all(contexts.map((context) => communicationClient.removeNotificationContext(context.id)))
- clearing = false
- } catch (e) {
- clearing = false
- console.error(e)
- }
- }
@@ -120,19 +101,7 @@
class:fly={$deviceInfo.navigator.float}
>
-
+
@@ -175,8 +144,4 @@
position: relative;
}
}
-
- .inbox-header {
- border-bottom: 1px solid var(--theme-navpanel-border);
- }
diff --git a/plugins/inbox-resources/src/components/InboxCard.svelte b/plugins/inbox-resources/src/components/InboxCard.svelte
index 4cdb36a21b..6faa878c74 100644
--- a/plugins/inbox-resources/src/components/InboxCard.svelte
+++ b/plugins/inbox-resources/src/components/InboxCard.svelte
@@ -84,7 +84,7 @@
flex-direction: column;
position: relative;
cursor: pointer;
- padding: 1rem 0.5rem 0.5rem;
+ padding: 0.5rem;
border-bottom: 1px solid var(--global-ui-BorderColor);
min-height: 5.625rem;
diff --git a/plugins/inbox-resources/src/components/InboxHeader.svelte b/plugins/inbox-resources/src/components/InboxHeader.svelte
new file mode 100644
index 0000000000..32f7de63e6
--- /dev/null
+++ b/plugins/inbox-resources/src/components/InboxHeader.svelte
@@ -0,0 +1,61 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/plugins/inbox-resources/src/components/InboxViewSettings.svelte b/plugins/inbox-resources/src/components/InboxViewSettings.svelte
new file mode 100644
index 0000000000..ff8b8c0e1d
--- /dev/null
+++ b/plugins/inbox-resources/src/components/InboxViewSettings.svelte
@@ -0,0 +1,120 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/plugins/inbox-resources/src/components/ReactionNotification.svelte b/plugins/inbox-resources/src/components/ReactionNotification.svelte
index 0b060e2fe9..c06222fb0f 100644
--- a/plugins/inbox-resources/src/components/ReactionNotification.svelte
+++ b/plugins/inbox-resources/src/components/ReactionNotification.svelte
@@ -44,7 +44,7 @@
{#if notification.message}
-
+
diff --git a/plugins/inbox-resources/src/components/preview/PreviewTemplate.svelte b/plugins/inbox-resources/src/components/preview/PreviewTemplate.svelte
index 73622467d5..d3e8fe9892 100644
--- a/plugins/inbox-resources/src/components/preview/PreviewTemplate.svelte
+++ b/plugins/inbox-resources/src/components/preview/PreviewTemplate.svelte
@@ -23,6 +23,7 @@
getPersonByPersonId
} from '@hcengineering/contact-resources'
import { SocialID } from '@hcengineering/communication-types'
+ import { isViewSettingEnabled, hideUserNamesSettingId, viewSettingsStore } from '../../settings'
export let tooltipLabel: IntlString | undefined = undefined
export let person: Person | undefined = undefined
@@ -32,10 +33,11 @@
export let kind: 'default' | 'column' = 'default'
export let padding: string | undefined = undefined
export let fixHeight: boolean = false
+ export let showSeparator: boolean = true
let clientWidth: number
- $: showPersonName = clientWidth > 300
+ $: showPersonName = clientWidth > 300 && !isViewSettingEnabled($viewSettingsStore, hideUserNamesSettingId)
let _person: Person | undefined
$: void updatePerson(socialId, person)
@@ -95,9 +97,11 @@
{/if}
{#if showPersonName && _person}
- {formatName(_person?.name ?? '')}
+ {formatName(_person?.name ?? '')}
+ {/if}
+ {#if showSeparator}
+ •
{/if}
- •
@@ -188,6 +192,10 @@
&__name {
font-weight: 500;
flex: 0 0 auto;
+
+ &.secondary {
+ color: var(--global-secondary-TextColor);
+ }
}
&__time {
diff --git a/plugins/inbox-resources/src/settings.ts b/plugins/inbox-resources/src/settings.ts
new file mode 100644
index 0000000000..38d3040d83
--- /dev/null
+++ b/plugins/inbox-resources/src/settings.ts
@@ -0,0 +1,85 @@
+// Copyright © 2025 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 { get, writable } from 'svelte/store'
+import { getCurrentAccount } from '@hcengineering/core'
+import { location } from '@hcengineering/ui'
+
+export interface ViewSetting {
+ id: string
+ on: boolean
+}
+
+export const hideUserNamesSettingId = 'view-settings.hide-user-names'
+
+export const viewSettingsStore = writable([])
+
+export function isViewSettingEnabled (settings: ViewSetting[], id: string): boolean {
+ const index = settings.findIndex((it) => it.id === id)
+ if (index === -1) return false
+ return settings[index].on
+}
+
+export function updateViewSetting (id: string, on: boolean): void {
+ const settings = get(viewSettingsStore)
+ const index = settings.findIndex((it) => it.id === id)
+ if (index === -1) {
+ viewSettingsStore.set([...settings, { id, on }])
+ } else {
+ viewSettingsStore.set(settings.map((it) => (it.id === id ? { ...it, on } : it)))
+ }
+}
+
+let prevWorkspace: string | undefined
+
+location.subscribe((loc) => {
+ const workspace = loc.path[1]
+ if (prevWorkspace !== workspace) {
+ initViewSettings()
+ prevWorkspace = workspace
+ }
+})
+
+viewSettingsStore.subscribe((settings) => {
+ const key = geLocalStorageKey()
+ if (key === undefined) return
+ localStorage.setItem(key, JSON.stringify(settings))
+})
+
+function geLocalStorageKey (): string | undefined {
+ const me = getCurrentAccount()
+ const loc = get(location)
+ const workspace = loc.path[1] ?? ''
+ if (me == null || workspace === '') return undefined
+ return `${workspace}.${me.uuid}.inbox.view-settings.v1`
+}
+
+function initViewSettings (): void {
+ const key = geLocalStorageKey()
+ if (key === undefined) return
+
+ const stored = localStorage.getItem(key)
+
+ if (stored == null) {
+ viewSettingsStore.set([])
+ return
+ }
+
+ try {
+ const data = JSON.parse(stored)
+ Array.isArray(data) ? viewSettingsStore.set(data as ViewSetting[]) : viewSettingsStore.set([])
+ } catch (e) {
+ console.error(e)
+ viewSettingsStore.set([])
+ }
+}