Chunter: saved messages (#1466)

Signed-off-by: budaeva <irina.budaeva@xored.com>
This commit is contained in:
budaeva 2022-04-21 12:39:49 +07:00 committed by GitHub
parent b684014b42
commit 1371131c4d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 238 additions and 25 deletions

View File

@ -39,6 +39,7 @@
"@anticrm/model-workbench": "~0.6.1",
"@anticrm/model-notification": "~0.6.0",
"@anticrm/activity": "~0.6.0",
"@anticrm/workbench": "~0.6.1"
"@anticrm/workbench": "~0.6.1",
"@anticrm/model-preference": "~0.6.0"
}
}

View File

@ -14,7 +14,7 @@
//
import activity from '@anticrm/activity'
import type { Backlink, Channel, ChunterMessage, Comment, Message, ThreadMessage } from '@anticrm/chunter'
import type { Backlink, Channel, ChunterMessage, Comment, Message, SavedMessages, ThreadMessage } from '@anticrm/chunter'
import contact, { Employee } from '@anticrm/contact'
import type { Account, Class, Doc, Domain, Ref, Space, Timestamp } from '@anticrm/core'
import { IndexKind } from '@anticrm/core'
@ -25,6 +25,7 @@ import view from '@anticrm/model-view'
import workbench from '@anticrm/model-workbench'
import chunter from './plugin'
import notification from '@anticrm/model-notification'
import preference, { TPreference } from '@anticrm/model-preference'
export const DOMAIN_CHUNTER = 'chunter' as Domain
export const DOMAIN_COMMENT = 'comment' as Domain
@ -100,8 +101,14 @@ export class TBacklink extends TComment implements Backlink {
backlinkClass!: Ref<Class<Doc>>
}
@Model(chunter.class.SavedMessages, preference.class.Preference)
export class TSavedMessages extends TPreference implements SavedMessages {
@Prop(TypeRef(chunter.class.ChunterMessage), chunter.string.SavedMessages)
attachedTo!: Ref<ChunterMessage>
}
export function createModel (builder: Builder): void {
builder.createModel(TChannel, TMessage, TThreadMessage, TChunterMessage, TComment, TBacklink)
builder.createModel(TChannel, TMessage, TThreadMessage, TChunterMessage, TComment, TBacklink, TSavedMessages)
builder.mixin(chunter.class.Channel, core.class.Class, workbench.mixin.SpaceView, {
view: {
class: chunter.class.Message
@ -239,6 +246,12 @@ export function createModel (builder: Builder): void {
icon: chunter.icon.Thread,
component: chunter.component.Threads,
position: 'top'
},
{
id: 'savedMessages',
label: chunter.string.SavedMessages,
icon: chunter.icon.Bookmark,
component: chunter.component.SavedMessages
}
],
spaces: [

View File

@ -27,7 +27,8 @@ export default mergeIds(chunterId, chunter, {
CommentPresenter: '' as AnyComponent,
ChannelPresenter: '' as AnyComponent,
Threads: '' as AnyComponent,
ThreadView: '' as AnyComponent
ThreadView: '' as AnyComponent,
SavedMessages: '' as AnyComponent
},
action: {
MarkCommentUnread: '' as Ref<Action>,
@ -55,7 +56,8 @@ export default mergeIds(chunterId, chunter, {
Edit: '' as IntlString,
MarkUnread: '' as IntlString,
LastMessage: '' as IntlString,
PinnedMessages: '' as IntlString
PinnedMessages: '' as IntlString,
SavedMessages: '' as IntlString
},
viewlet: {
Chat: '' as Ref<ViewletDescriptor>

View File

@ -11,4 +11,7 @@
<symbol id="thread" viewBox="0 0 16 16">
<path d="M8,14.5c-0.9,0-1.9-0.2-2.7-0.6c-0.2-0.1-0.5-0.2-0.6-0.2c-0.2,0-0.4,0.1-0.7,0.2c-0.5,0.2-1.2,0.4-1.7-0.1 c-0.5-0.5-0.3-1.2-0.1-1.7c0.1-0.3,0.2-0.5,0.2-0.7c0-0.2-0.1-0.4-0.2-0.7C1,8.3,1.5,5.3,3.4,3.4C4.6,2.2,6.3,1.5,8,1.5 s3.4,0.7,4.6,1.9c2.5,2.5,2.5,6.7,0,9.2l0,0C11.4,13.8,9.7,14.5,8,14.5z M4.6,12.7c0.4,0,0.7,0.1,1,0.3c2.1,1,4.6,0.5,6.2-1.1 c2.1-2.1,2.1-5.6,0-7.8c-1-1-2.4-1.6-3.9-1.6c-1.5,0-2.9,0.6-3.9,1.6C2.5,5.7,2,8.2,3,10.3c0.1,0.4,0.3,0.7,0.3,1.1 c0,0.4-0.1,0.7-0.2,1C3,12.6,2.9,13,2.9,13.1C3,13.2,3.4,13,3.6,12.9C3.9,12.8,4.3,12.7,4.6,12.7z M12.2,12.2L12.2,12.2L12.2,12.2z"/>
</symbol>
<symbol id="bookmark" viewBox="0 0 16 16">
<path d="M16.2,18c-0.1,0-0.2,0-0.3-0.1l-6-4l-6,4c-0.2,0.1-0.4,0.1-0.5,0c-0.2-0.1-0.3-0.3-0.3-0.4V4.2 C3.2,3,4.3,2,5.5,2h8.9c1.3,0,2.3,1,2.3,2.2v13.3c0,0.2-0.1,0.4-0.3,0.4C16.4,18,16.3,18,16.2,18z M10,12.8c0.1,0,0.2,0,0.3,0.1 l5.5,3.6V4.2c0-0.6-0.6-1.2-1.3-1.2H5.5C4.8,3,4.2,3.5,4.2,4.2v12.4l5.5-3.6C9.8,12.9,9.9,12.8,10,12.8z"/>
</symbol>
</svg>

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@ -44,6 +44,11 @@
"ArchiveChannel": "Archive channel",
"UnarchiveChannel": "Unarchive channel",
"ArchiveConfirm": "Do you want to archive channel?",
"UnarchiveConfirm": "Do you want to unarchive channel?"
"UnarchiveConfirm": "Do you want to unarchive channel?",
"SavedMessages": "Saved messages",
"AddToSaved": "Add to saved",
"RemoveFromSaved": "Remove from saved",
"EmptySavedHeader": "Add messages to come back to later",
"EmptySavedText": "Tick off your to-dos or save something for another time. Only you can see your saved items, so use them however you like."
}
}

View File

@ -43,6 +43,11 @@
"ArchiveChannel": "Архивировать канал",
"UnarchiveChannel": "Разархивировать канал",
"ArchiveConfirm": "Вы действительно хотите архивировать канал?",
"UnarchiveConfirm": "Вы действительно хотите разархивировать канал?"
"UnarchiveConfirm": "Вы действительно хотите разархивировать канал?",
"SavedMessages": "Сохраненные сообщения",
"AddToSaved": "Добавить в сохраненные",
"RemoveFromSaved": "Удалить из сохраненных",
"EmptySavedHeader": "Добавляйте сообщения и файлы, чтобы вернуться к ним позже",
"EmptySavedText": "Пометьте свои задачи или сохраните что-нибудь на потом. Только вы можете просматривать свои сохраненные объекты, поэтому используйте их как угодно."
}
}

View File

@ -21,7 +21,8 @@ loadMetadata(chunter.icon, {
Chunter: `${icons}#chunter`,
Hashtag: `${icons}#hashtag`,
Thread: `${icons}#thread`,
Lock: `${icons}#lock`
Lock: `${icons}#lock`,
Bookmark: `${icons}#bookmark`
})
addStringsLoader(chunterId, async (lang: string) => await import(`../lang/${lang}.json`))

View File

@ -46,6 +46,7 @@
"@anticrm/attachment-resources": "~0.6.0",
"@anticrm/view-resources": "~0.6.0",
"@anticrm/view": "~0.6.0",
"@anticrm/workbench": "~0.6.1"
"@anticrm/workbench": "~0.6.1",
"@anticrm/preference": "~0.6.0"
}
}

View File

@ -26,6 +26,7 @@
export let space: Ref<Space> | undefined
export let pinnedIds: Ref<ChunterMessage>[]
export let savedIds: Ref<ChunterMessage>[]
let div: HTMLDivElement | undefined
let autoscroll: boolean = false
@ -113,7 +114,13 @@
{#if newMessagesPos === i}
<ChannelSeparator title={chunter.string.New} line reverse isNew />
{/if}
<MessageComponent {message} {employees} on:openThread isPinned={pinnedIds.includes(message._id)} />
<MessageComponent
{message}
{employees}
on:openThread
isPinned={pinnedIds.includes(message._id)}
isSaved={savedIds.includes(message._id)}
/>
{/each}
{/if}
</div>

View File

@ -75,6 +75,12 @@
},
{ limit: 1 }
)
const preferenceQuery = createQuery()
let savedIds: Ref<ChunterMessage>[] = []
preferenceQuery.query(chunter.class.SavedMessages, {}, (res) => {
savedIds = res.map((r) => r.attachedTo)
})
</script>
<PinnedMessages {space} {pinnedIds} />
@ -84,6 +90,7 @@
openThread(e.detail)
}}
{pinnedIds}
{savedIds}
/>
<div class="reference">
<AttachmentRefInput {space} {_class} objectId={_id} on:message={onMessage} />

View File

@ -1,7 +1,7 @@
<script lang="ts">
import { Channel } from '@anticrm/chunter'
import { Button } from '@anticrm/ui'
import { createEventDispatcher } from 'svelte';
import { createEventDispatcher } from 'svelte'
import { ArchiveChannel } from '../index'
import chunter from '../plugin'

View File

@ -25,7 +25,7 @@
import { Action } from '@anticrm/view'
import { getActions } from '@anticrm/view-resources'
import { createEventDispatcher } from 'svelte'
import { UnpinMessage } from '../index'
import { AddToSaved, DeleteFromSaved, UnpinMessage } from '../index'
import chunter from '../plugin'
import { getTime } from '../utils'
// import Share from './icons/Share.svelte'
@ -39,6 +39,7 @@
export let employees: Map<Ref<Employee>, Employee>
export let thread: boolean = false
export let isPinned: boolean = false
export let isSaved: boolean = false
let refInput: AttachmentRefInput
@ -85,6 +86,7 @@
action: async () => {
;(await client.findAll(chunter.class.ThreadMessage, { attachedTo: message._id as Ref<Message> })).forEach((c) => {
UnpinMessage(c)
DeleteFromSaved(c)
})
UnpinMessage(message)
await client.removeDoc(message._class, message.space, message._id)
@ -142,6 +144,11 @@
dispatch('openThread', message._id)
}
function addToSaved () {
if (isSaved) DeleteFromSaved(message)
else AddToSaved(message)
}
$: parentMessage = message as Message
$: hasReplies = (parentMessage?.replies?.length ?? 0) > 0
</script>
@ -153,9 +160,9 @@
{#if employee}{formatName(employee.name)}{/if}
<span>{getTime(message.createOn)}</span>
{#if message.editedOn}
<span>
<span>
<Tooltip label={ui.string.TimeTooltip} props={{ value: getTime(message.editedOn) }}>
<Label label={chunter.string.Edited}/>
<Label label={chunter.string.Edited} />
</Tooltip>
</span>
{/if}
@ -171,10 +178,7 @@
on:message={onMessageEdit}
/>
<div class="flex-row-reverse gap-2 reverse">
<Button
label={chunter.string.EditCancel}
on:click={() => isEditing = false}
/>
<Button label={chunter.string.EditCancel} on:click={() => (isEditing = false)} />
<Button label={chunter.string.EditUpdate} on:click={() => refInput.submit()} />
</div>
{:else}
@ -203,7 +207,14 @@
{#if !thread}
<div class="tool"><ActionIcon icon={Thread} size={'medium'} action={openThread} /></div>
{/if}
<div class="tool"><ActionIcon icon={Bookmark} size={'medium'} /></div>
<div class="tool book">
<ActionIcon
icon={Bookmark}
size={'medium'}
action={addToSaved}
label={isSaved ? chunter.string.RemoveFromSaved : chunter.string.AddToSaved}
/>
</div>
<!-- <div class="tool"><ActionIcon icon={Share} size={'medium'}/></div> -->
<div class="tool"><ActionIcon icon={Emoji} size={'medium'} /></div>
</div>

View File

@ -0,0 +1,108 @@
<script lang="ts">
import { createQuery } from '@anticrm/presentation'
import { ChunterMessage } from '@anticrm/chunter'
import { Ref } from '@anticrm/core'
import Message from './Message.svelte'
import contact, { Employee } from '@anticrm/contact'
import { getCurrentLocation, Label, navigate } from '@anticrm/ui'
import Bookmark from './icons/Bookmark.svelte'
import chunter from '../plugin'
let savedIds: Ref<ChunterMessage>[] = []
let savedMessages: ChunterMessage[] = []
const messagesQuery = createQuery()
const preferenceQuery = createQuery()
preferenceQuery.query(chunter.class.SavedMessages, {}, (res) => {
savedIds = res.map((r) => r.attachedTo)
})
$: savedIds &&
messagesQuery.query(chunter.class.ChunterMessage, { _id: { $in: savedIds } }, (res) => {
savedMessages = res
})
let employees: Map<Ref<Employee>, Employee> = new Map<Ref<Employee>, Employee>()
const employeeQuery = createQuery()
employeeQuery.query(
contact.class.Employee,
{},
(res) =>
(employees = new Map(
res.map((r) => {
return [r._id, r]
})
))
)
const pinnedQuery = createQuery()
const pinnedIds: Ref<ChunterMessage>[] = []
pinnedQuery.query(
chunter.class.Channel,
{},
(res) => {
res.forEach((ch) => pinnedIds.concat(ch?.pinned ?? []))
},
{ limit: 1 }
)
function openMessage (message: ChunterMessage) {
const loc = getCurrentLocation()
if (message.attachedToClass === chunter.class.Channel) {
loc.path.length = 3
loc.path[2] = message.attachedTo
} else if (message.attachedToClass === chunter.class.Message) {
loc.path.length = 4
loc.path[2] = message.space
loc.path[3] = message.attachedTo
}
navigate(loc)
}
</script>
{#if savedMessages.length > 0}
{#each savedMessages as message}
<div on:click={() => openMessage(message)}>
<Message
{message}
{employees}
on:openThread
thread
isPinned={pinnedIds.includes(message._id)}
isSaved={savedIds.includes(message._id)}
/>
</div>
{/each}
{:else}
<div class="empty">
<Bookmark size={'large'} />
<div class="an-element__label header">
<Label label={chunter.string.EmptySavedHeader} />
</div>
<span class="an-element__label">
<Label label={chunter.string.EmptySavedText} />
</span>
</div>
{/if}
<style lang="scss">
.empty {
display: flex;
align-self: center;
flex-direction: column;
justify-content: center;
align-items: center;
text-align: center;
height: inherit;
width: 30rem;
}
.header {
font-weight: 600;
margin: 1rem;
}
</style>

View File

@ -123,6 +123,13 @@
))
)
const preferenceQuery = createQuery()
let savedIds: Ref<ChunterMessage>[] = []
preferenceQuery.query(chunter.class.SavedMessages, {}, (res) => {
savedIds = res.map((r) => r.attachedTo)
})
async function onMessage (event: CustomEvent) {
const { message, attachments } = event.detail
const me = getCurrentAccount()._id
@ -193,7 +200,13 @@
{#if newMessagesPos === i}
<ChannelSeparator title={chunter.string.New} line reverse isNew />
{/if}
<MsgView message={comment} {employees} thread isPinned={pinnedIds.includes(comment._id)} />
<MsgView
message={comment}
{employees}
thread
isPinned={pinnedIds.includes(comment._id)}
isSaved={savedIds.includes(comment._id)}
/>
{/each}
{/if}
</div>

View File

@ -32,6 +32,8 @@ import CreateChannel from './components/CreateChannel.svelte'
import EditChannel from './components/EditChannel.svelte'
import ThreadView from './components/ThreadView.svelte'
import Threads from './components/Threads.svelte'
import SavedMessages from './components/SavedMessages.svelte'
import preference from '@anticrm/preference'
export { CommentsPresenter }
@ -125,6 +127,24 @@ async function UnarchiveChannel (channel: Channel): Promise<void> {
}
)
}
export async function AddToSaved (message: ChunterMessage): Promise<void> {
const client = getClient()
await client.createDoc(chunter.class.SavedMessages, preference.space.Preference, {
attachedTo: message._id
})
}
export async function DeleteFromSaved (message: ChunterMessage): Promise<void> {
const client = getClient()
const current = await client.findOne(chunter.class.SavedMessages, { attachedTo: message._id })
if (current !== undefined) {
await client.remove(current)
}
}
export default async (): Promise<Resources> => ({
component: {
CommentInput,
@ -136,7 +156,8 @@ export default async (): Promise<Resources> => ({
ChannelPresenter,
EditChannel,
Threads,
ThreadView
ThreadView,
SavedMessages
},
activity: {
TxCommentCreate,

View File

@ -61,6 +61,10 @@ export default mergeIds(chunterId, chunter, {
EditMessage: '' as IntlString,
Edited: '' as IntlString,
AndYou: '' as IntlString,
ShowMoreReplies: '' as IntlString
ShowMoreReplies: '' as IntlString,
AddToSaved: '' as IntlString,
RemoveFromSaved: '' as IntlString,
EmptySavedHeader: '' as IntlString,
EmptySavedText: '' as IntlString
}
})

View File

@ -29,6 +29,7 @@
"@anticrm/platform": "~0.6.5",
"@anticrm/ui": "~0.6.0",
"@anticrm/contact": "~0.6.5",
"@anticrm/core": "~0.6.16"
"@anticrm/core": "~0.6.16",
"@anticrm/preference": "~0.6.0"
}
}

View File

@ -17,6 +17,7 @@ import type { Account, AttachedDoc, Class, Doc, Ref, Space, Timestamp } from '@a
import type { Employee } from '@anticrm/contact'
import type { Asset, Plugin } from '@anticrm/platform'
import { IntlString, plugin } from '@anticrm/platform'
import type { Preference } from '@anticrm/preference'
import { AnyComponent } from '@anticrm/ui'
/**
@ -82,6 +83,13 @@ export interface Backlink extends Comment {
attachedDocId?: Ref<Doc>
}
/**
* @public
*/
export interface SavedMessages extends Preference {
attachedTo: Ref<ChunterMessage>
}
/**
* @public
*/
@ -92,7 +100,8 @@ export default plugin(chunterId, {
Chunter: '' as Asset,
Hashtag: '' as Asset,
Thread: '' as Asset,
Lock: '' as Asset
Lock: '' as Asset,
Bookmark: '' as Asset
},
component: {
CommentInput: '' as AnyComponent,
@ -104,7 +113,8 @@ export default plugin(chunterId, {
ThreadMessage: '' as Ref<Class<ThreadMessage>>,
Backlink: '' as Ref<Class<Backlink>>,
Comment: '' as Ref<Class<Comment>>,
Channel: '' as Ref<Class<Channel>>
Channel: '' as Ref<Class<Channel>>,
SavedMessages: '' as Ref<Class<SavedMessages>>
},
space: {
Backlinks: '' as Ref<Space>