mirror of
https://github.com/hcengineering/platform.git
synced 2025-03-19 13:18:56 +00:00
Merge remote-tracking branch 'origin/develop' into staging
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
commit
71ad788949
@ -19,6 +19,7 @@ import notification, { notificationActionTemplates } from '@hcengineering/model-
|
|||||||
import activity from '@hcengineering/activity'
|
import activity from '@hcengineering/activity'
|
||||||
import workbench from '@hcengineering/model-workbench'
|
import workbench from '@hcengineering/model-workbench'
|
||||||
import core from '@hcengineering/model-core'
|
import core from '@hcengineering/model-core'
|
||||||
|
import contact from '@hcengineering/contact'
|
||||||
|
|
||||||
import chunter from './plugin'
|
import chunter from './plugin'
|
||||||
|
|
||||||
@ -43,6 +44,18 @@ export function defineActions (builder: Builder): void {
|
|||||||
chunter.category.Chunter
|
chunter.category.Chunter
|
||||||
)
|
)
|
||||||
|
|
||||||
|
createAction(builder, {
|
||||||
|
action: chunter.actionImpl.StartConversation,
|
||||||
|
label: chunter.string.StartConversation,
|
||||||
|
icon: chunter.icon.Thread,
|
||||||
|
input: 'focus',
|
||||||
|
category: chunter.category.Chunter,
|
||||||
|
target: contact.mixin.Employee,
|
||||||
|
context: {
|
||||||
|
mode: 'context',
|
||||||
|
group: 'associate'
|
||||||
|
}
|
||||||
|
})
|
||||||
defineMessageActions(builder)
|
defineMessageActions(builder)
|
||||||
defineChannelActions(builder)
|
defineChannelActions(builder)
|
||||||
}
|
}
|
||||||
|
@ -53,7 +53,8 @@ export default mergeIds(chunterId, chunter, {
|
|||||||
ReplyToThread: '' as ViewAction,
|
ReplyToThread: '' as ViewAction,
|
||||||
OpenInSidebar: '' as ViewAction,
|
OpenInSidebar: '' as ViewAction,
|
||||||
TranslateMessage: '' as ViewAction,
|
TranslateMessage: '' as ViewAction,
|
||||||
ShowOriginalMessage: '' as ViewAction
|
ShowOriginalMessage: '' as ViewAction,
|
||||||
|
StartConversation: '' as ViewAction
|
||||||
},
|
},
|
||||||
category: {
|
category: {
|
||||||
Chunter: '' as Ref<ActionCategory>
|
Chunter: '' as Ref<ActionCategory>
|
||||||
|
@ -27,6 +27,7 @@
|
|||||||
|
|
||||||
import { getFileUrl } from '../file'
|
import { getFileUrl } from '../file'
|
||||||
import { getPreviewType, previewTypes } from '../filetypes'
|
import { getPreviewType, previewTypes } from '../filetypes'
|
||||||
|
import { imageSizeToRatio } from '../image'
|
||||||
import { BlobMetadata, FilePreviewExtension } from '../types'
|
import { BlobMetadata, FilePreviewExtension } from '../types'
|
||||||
|
|
||||||
export let file: Ref<Blob>
|
export let file: Ref<Blob>
|
||||||
@ -68,8 +69,8 @@
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
const pR: number = mD?.pixelRatio ?? 1
|
const pR: number = mD?.pixelRatio ?? 1
|
||||||
const fWidth: number = mD.originalWidth / pR
|
const fWidth: number = imageSizeToRatio(mD.originalWidth, pR)
|
||||||
const fHeight: number = mD.originalHeight / pR
|
const fHeight: number = imageSizeToRatio(mD.originalHeight, pR)
|
||||||
let mHeight: number = 0
|
let mHeight: number = 0
|
||||||
let scale: number = 1
|
let scale: number = 1
|
||||||
if (fWidth > pWidth) {
|
if (fWidth > pWidth) {
|
||||||
|
@ -15,6 +15,11 @@
|
|||||||
|
|
||||||
import extract from 'png-chunks-extract'
|
import extract from 'png-chunks-extract'
|
||||||
|
|
||||||
|
export function imageSizeToRatio (width: number, pixelRatio: number): number {
|
||||||
|
// consider pixel ratio < 2 as non retina and display them in original size
|
||||||
|
return pixelRatio < 2 ? width : Math.round(width / pixelRatio)
|
||||||
|
}
|
||||||
|
|
||||||
export async function getImageSize (file: Blob): Promise<{ width: number, height: number, pixelRatio: number }> {
|
export async function getImageSize (file: Blob): Promise<{ width: number, height: number, pixelRatio: number }> {
|
||||||
const size = isPng(file) ? await getPngImageSize(file) : undefined
|
const size = isPng(file) ? await getPngImageSize(file) : undefined
|
||||||
|
|
||||||
@ -65,20 +70,18 @@ async function getPngImageSize (file: Blob): Promise<{ width: number, height: nu
|
|||||||
const idhrData = parseIHDR(new DataView(iHDRChunk.data.buffer))
|
const idhrData = parseIHDR(new DataView(iHDRChunk.data.buffer))
|
||||||
const physData = parsePhys(new DataView(pHYsChunk.data.buffer))
|
const physData = parsePhys(new DataView(pHYsChunk.data.buffer))
|
||||||
|
|
||||||
if (physData.unit === 0 && physData.ppux === physData.ppuy) {
|
// Assuming pixels are square
|
||||||
const pixelRatio = Math.round(physData.ppux / 2834.5)
|
// http://www.libpng.org/pub/png/spec/1.2/PNG-Decoders.html#D.Pixel-dimensions
|
||||||
return {
|
const pixelRatio = Math.round(physData.ppux * 0.0254) / 72
|
||||||
width: idhrData.width,
|
return {
|
||||||
height: idhrData.height,
|
width: idhrData.width,
|
||||||
pixelRatio
|
height: idhrData.height,
|
||||||
}
|
pixelRatio
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err)
|
console.error(err)
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
return undefined
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// See http://www.libpng.org/pub/png/spec/1.2/PNG-Chunks.html
|
// See http://www.libpng.org/pub/png/spec/1.2/PNG-Chunks.html
|
||||||
|
@ -148,6 +148,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.icon {
|
.icon {
|
||||||
|
writing-mode: initial;
|
||||||
|
text-orientation: initial;
|
||||||
&.vertical {
|
&.vertical {
|
||||||
transform: rotate(90deg);
|
transform: rotate(90deg);
|
||||||
text-orientation: upright;
|
text-orientation: upright;
|
||||||
|
@ -126,6 +126,7 @@
|
|||||||
"TranslateMessage": "Translate message",
|
"TranslateMessage": "Translate message",
|
||||||
"Translate": "Translate",
|
"Translate": "Translate",
|
||||||
"ShowOriginal": "Show original",
|
"ShowOriginal": "Show original",
|
||||||
"Translating": "Translating..."
|
"Translating": "Translating...",
|
||||||
|
"StartConversation": "Start conversation"
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -126,6 +126,7 @@
|
|||||||
"TranslateMessage": "Traducir mensaje",
|
"TranslateMessage": "Traducir mensaje",
|
||||||
"Translate": "Traducir",
|
"Translate": "Traducir",
|
||||||
"ShowOriginal": "Mostrar original",
|
"ShowOriginal": "Mostrar original",
|
||||||
"Translating": "Traduciendo..."
|
"Translating": "Traduciendo...",
|
||||||
|
"StartConversation": "Iniciar conversación"
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -126,6 +126,7 @@
|
|||||||
"TranslateMessage": "Traduire le message",
|
"TranslateMessage": "Traduire le message",
|
||||||
"Translate": "Traduire",
|
"Translate": "Traduire",
|
||||||
"ShowOriginal": "Afficher l'original",
|
"ShowOriginal": "Afficher l'original",
|
||||||
"Translating": "Traduction en cours..."
|
"Translating": "Traduction en cours...",
|
||||||
|
"StartConversation": "Démarrer la conversation"
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -126,6 +126,7 @@
|
|||||||
"TranslateMessage": "Traduzir mensagem",
|
"TranslateMessage": "Traduzir mensagem",
|
||||||
"Translate": "Traduzir",
|
"Translate": "Traduzir",
|
||||||
"ShowOriginal": "Mostrar original",
|
"ShowOriginal": "Mostrar original",
|
||||||
"Translating": "A traduzir..."
|
"Translating": "A traduzir...",
|
||||||
|
"StartConversation": "Iniciar conversa"
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -126,6 +126,7 @@
|
|||||||
"TranslateMessage": "Перевести сообщение",
|
"TranslateMessage": "Перевести сообщение",
|
||||||
"Translate": "Перевести",
|
"Translate": "Перевести",
|
||||||
"ShowOriginal": "Показать оригинал",
|
"ShowOriginal": "Показать оригинал",
|
||||||
"Translating": "Перевод..."
|
"Translating": "Перевод...",
|
||||||
|
"StartConversation": "Начать диалог"
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -126,6 +126,7 @@
|
|||||||
"TranslateMessage": "翻译消息",
|
"TranslateMessage": "翻译消息",
|
||||||
"Translate": "翻译",
|
"Translate": "翻译",
|
||||||
"ShowOriginal": "显示原文",
|
"ShowOriginal": "显示原文",
|
||||||
"Translating": "翻译中..."
|
"Translating": "翻译中...",
|
||||||
|
"StartConversation": "开始对话"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,24 +14,20 @@
|
|||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { createEventDispatcher, onMount } from 'svelte'
|
import { createEventDispatcher, onMount } from 'svelte'
|
||||||
import { deepEqual } from 'fast-equals'
|
|
||||||
|
|
||||||
import { DirectMessage } from '@hcengineering/chunter'
|
import contact, { Employee, PersonAccount } from '@hcengineering/contact'
|
||||||
import contact, { Employee, Person, PersonAccount } from '@hcengineering/contact'
|
import { Ref } from '@hcengineering/core'
|
||||||
import core, { getCurrentAccount, Ref } from '@hcengineering/core'
|
import { SelectUsersPopup } from '@hcengineering/contact-resources'
|
||||||
import { personIdByAccountId, SelectUsersPopup } from '@hcengineering/contact-resources'
|
|
||||||
import notification from '@hcengineering/notification'
|
|
||||||
import presentation, { createQuery, getClient } from '@hcengineering/presentation'
|
import presentation, { createQuery, getClient } from '@hcengineering/presentation'
|
||||||
import { Modal, showPopup } from '@hcengineering/ui'
|
import { Modal, showPopup } from '@hcengineering/ui'
|
||||||
|
|
||||||
import chunter from '../../../plugin'
|
import chunter from '../../../plugin'
|
||||||
import { buildDmName } from '../../../utils'
|
import { buildDmName, createDirect } from '../../../utils'
|
||||||
import ChannelMembers from '../../ChannelMembers.svelte'
|
import ChannelMembers from '../../ChannelMembers.svelte'
|
||||||
import { openChannel } from '../../../navigation'
|
import { openChannel } from '../../../navigation'
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
const client = getClient()
|
const client = getClient()
|
||||||
const myAcc = getCurrentAccount() as PersonAccount
|
|
||||||
const query = createQuery()
|
const query = createQuery()
|
||||||
|
|
||||||
let employeeIds: Ref<Employee>[] = []
|
let employeeIds: Ref<Employee>[] = []
|
||||||
@ -51,74 +47,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function createDirectMessage (): Promise<void> {
|
async function createDirectMessage (): Promise<void> {
|
||||||
const employeeAccounts = await client.findAll(contact.class.PersonAccount, { person: { $in: employeeIds } })
|
const dmId = await createDirect(employeeIds)
|
||||||
const accIds = [myAcc._id, ...employeeAccounts.filter(({ _id }) => _id !== myAcc._id).map(({ _id }) => _id)].sort()
|
|
||||||
|
|
||||||
const existingDms = await client.findAll(chunter.class.DirectMessage, {})
|
if (dmId !== undefined) {
|
||||||
const newDirectPersons = Array.from(new Set([...employeeIds, myAcc.person])).sort()
|
|
||||||
|
|
||||||
let direct: DirectMessage | undefined
|
|
||||||
|
|
||||||
for (const dm of existingDms) {
|
|
||||||
const existDirectPersons = Array.from(
|
|
||||||
new Set(dm.members.map((id) => $personIdByAccountId.get(id as Ref<PersonAccount>)))
|
|
||||||
)
|
|
||||||
.filter((person): person is Ref<Person> => person !== undefined)
|
|
||||||
.sort()
|
|
||||||
if (deepEqual(existDirectPersons, newDirectPersons)) {
|
|
||||||
direct = dm
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const existingMembers = direct?.members
|
|
||||||
const missingAccounts = existingMembers !== undefined ? accIds.filter((id) => !existingMembers.includes(id)) : []
|
|
||||||
|
|
||||||
if (direct !== undefined && missingAccounts.length > 0) {
|
|
||||||
await client.updateDoc(chunter.class.DirectMessage, direct.space, direct._id, {
|
|
||||||
$push: { members: { $each: missingAccounts, $position: 0 } }
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const dmId =
|
|
||||||
direct?._id ??
|
|
||||||
(await client.createDoc(chunter.class.DirectMessage, core.space.Space, {
|
|
||||||
name: '',
|
|
||||||
description: '',
|
|
||||||
private: true,
|
|
||||||
archived: false,
|
|
||||||
members: accIds
|
|
||||||
}))
|
|
||||||
|
|
||||||
const context = await client.findOne(notification.class.DocNotifyContext, {
|
|
||||||
user: myAcc._id,
|
|
||||||
objectId: dmId,
|
|
||||||
objectClass: chunter.class.DirectMessage
|
|
||||||
})
|
|
||||||
|
|
||||||
if (context !== undefined) {
|
|
||||||
if (context.hidden) {
|
|
||||||
await client.updateDoc(context._class, context.space, context._id, { hidden: false })
|
|
||||||
}
|
|
||||||
|
|
||||||
dispatch('close')
|
|
||||||
openChannel(dmId, chunter.class.DirectMessage)
|
openChannel(dmId, chunter.class.DirectMessage)
|
||||||
return
|
dispatch('close')
|
||||||
}
|
}
|
||||||
|
|
||||||
const space = await client.findOne(contact.class.PersonSpace, { person: myAcc.person }, { projection: { _id: 1 } })
|
|
||||||
if (!space) return
|
|
||||||
await client.createDoc(notification.class.DocNotifyContext, space._id, {
|
|
||||||
user: myAcc._id,
|
|
||||||
objectId: dmId,
|
|
||||||
objectClass: chunter.class.DirectMessage,
|
|
||||||
objectSpace: core.space.Space,
|
|
||||||
hidden: false,
|
|
||||||
isPinned: false
|
|
||||||
})
|
|
||||||
|
|
||||||
openChannel(dmId, chunter.class.DirectMessage)
|
|
||||||
dispatch('close')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleCancel (): void {
|
function handleCancel (): void {
|
||||||
|
@ -78,7 +78,8 @@ import {
|
|||||||
removeChannelAction,
|
removeChannelAction,
|
||||||
translateMessage,
|
translateMessage,
|
||||||
showOriginalMessage,
|
showOriginalMessage,
|
||||||
canTranslateMessage
|
canTranslateMessage,
|
||||||
|
startConversationAction
|
||||||
} from './utils'
|
} from './utils'
|
||||||
|
|
||||||
export { default as ChatMessageInput } from './components/chat-message/ChatMessageInput.svelte'
|
export { default as ChatMessageInput } from './components/chat-message/ChatMessageInput.svelte'
|
||||||
@ -213,6 +214,7 @@ export default async (): Promise<Resources> => ({
|
|||||||
ReplyToThread: replyToThread,
|
ReplyToThread: replyToThread,
|
||||||
OpenInSidebar: openChannelInSidebarAction,
|
OpenInSidebar: openChannelInSidebarAction,
|
||||||
TranslateMessage: translateMessage,
|
TranslateMessage: translateMessage,
|
||||||
ShowOriginalMessage: showOriginalMessage
|
ShowOriginalMessage: showOriginalMessage,
|
||||||
|
StartConversation: startConversationAction
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -22,7 +22,7 @@ import activity, {
|
|||||||
import { isReactionMessage } from '@hcengineering/activity-resources'
|
import { isReactionMessage } from '@hcengineering/activity-resources'
|
||||||
import { type Channel, type ChatMessage, type DirectMessage, type ThreadMessage } from '@hcengineering/chunter'
|
import { type Channel, type ChatMessage, type DirectMessage, type ThreadMessage } from '@hcengineering/chunter'
|
||||||
import contact, { getName, type Employee, type Person, type PersonAccount } from '@hcengineering/contact'
|
import contact, { getName, type Employee, type Person, type PersonAccount } from '@hcengineering/contact'
|
||||||
import { PersonIcon, employeeByIdStore } from '@hcengineering/contact-resources'
|
import { PersonIcon, employeeByIdStore, personIdByAccountId } from '@hcengineering/contact-resources'
|
||||||
import core, {
|
import core, {
|
||||||
getCurrentAccount,
|
getCurrentAccount,
|
||||||
type Account,
|
type Account,
|
||||||
@ -35,7 +35,7 @@ import core, {
|
|||||||
type Timestamp,
|
type Timestamp,
|
||||||
type WithLookup
|
type WithLookup
|
||||||
} from '@hcengineering/core'
|
} from '@hcengineering/core'
|
||||||
import { type DocNotifyContext, type InboxNotification } from '@hcengineering/notification'
|
import notification, { type DocNotifyContext, type InboxNotification } from '@hcengineering/notification'
|
||||||
import {
|
import {
|
||||||
InboxNotificationsClientImpl,
|
InboxNotificationsClientImpl,
|
||||||
isActivityNotification,
|
isActivityNotification,
|
||||||
@ -48,10 +48,11 @@ import { classIcon, getDocLinkTitle, getDocTitle } from '@hcengineering/view-res
|
|||||||
import { get, writable, type Unsubscriber } from 'svelte/store'
|
import { get, writable, type Unsubscriber } from 'svelte/store'
|
||||||
import aiBot from '@hcengineering/ai-bot'
|
import aiBot from '@hcengineering/ai-bot'
|
||||||
import { translate as aiTranslate } from '@hcengineering/ai-bot-resources'
|
import { translate as aiTranslate } from '@hcengineering/ai-bot-resources'
|
||||||
|
import { deepEqual } from 'fast-equals'
|
||||||
|
|
||||||
import ChannelIcon from './components/ChannelIcon.svelte'
|
import ChannelIcon from './components/ChannelIcon.svelte'
|
||||||
import DirectIcon from './components/DirectIcon.svelte'
|
import DirectIcon from './components/DirectIcon.svelte'
|
||||||
import { resetChunterLocIfEqual } from './navigation'
|
import { openChannelInSidebar, resetChunterLocIfEqual } from './navigation'
|
||||||
import chunter from './plugin'
|
import chunter from './plugin'
|
||||||
import { shownTranslatedMessagesStore, translatedMessagesStore, translatingMessagesStore } from './stores'
|
import { shownTranslatedMessagesStore, translatedMessagesStore, translatingMessagesStore } from './stores'
|
||||||
|
|
||||||
@ -560,3 +561,90 @@ export async function canTranslateMessage (): Promise<boolean> {
|
|||||||
const url = getMetadata(aiBot.metadata.EndpointURL) ?? ''
|
const url = getMetadata(aiBot.metadata.EndpointURL) ?? ''
|
||||||
return url !== ''
|
return url !== ''
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function startConversationAction (docs?: Employee | Employee[]): Promise<void> {
|
||||||
|
if (docs === undefined) return
|
||||||
|
const employees = Array.isArray(docs) ? docs : [docs]
|
||||||
|
const employeeIds = employees.map(({ _id }) => _id)
|
||||||
|
|
||||||
|
const dm = await createDirect(employeeIds)
|
||||||
|
|
||||||
|
if (dm !== undefined) {
|
||||||
|
await openChannelInSidebar(dm, chunter.class.DirectMessage, undefined, undefined, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createDirect (employeeIds: Array<Ref<Employee>>): Promise<Ref<DirectMessage>> {
|
||||||
|
const client = getClient()
|
||||||
|
const me = getCurrentAccount()
|
||||||
|
|
||||||
|
const employeeAccounts = await client.findAll(contact.class.PersonAccount, { person: { $in: employeeIds } })
|
||||||
|
const accIds = [me._id, ...employeeAccounts.filter(({ _id }) => _id !== me._id).map(({ _id }) => _id)].sort()
|
||||||
|
|
||||||
|
const existingDms = await client.findAll(chunter.class.DirectMessage, {})
|
||||||
|
const newDirectPersons = Array.from(new Set([...employeeIds, me.person] as Array<Ref<Person>>)).sort()
|
||||||
|
|
||||||
|
let direct: DirectMessage | undefined
|
||||||
|
|
||||||
|
for (const dm of existingDms) {
|
||||||
|
const existDirectPersons = Array.from(
|
||||||
|
new Set(dm.members.map((id) => get(personIdByAccountId).get(id as Ref<PersonAccount>)))
|
||||||
|
)
|
||||||
|
.filter((person): person is Ref<Person> => person !== undefined)
|
||||||
|
.sort()
|
||||||
|
if (deepEqual(existDirectPersons, newDirectPersons)) {
|
||||||
|
direct = dm
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const existingMembers = direct?.members
|
||||||
|
const missingAccounts = existingMembers !== undefined ? accIds.filter((id) => !existingMembers.includes(id)) : []
|
||||||
|
|
||||||
|
if (direct !== undefined && missingAccounts.length > 0) {
|
||||||
|
await client.updateDoc(chunter.class.DirectMessage, direct.space, direct._id, {
|
||||||
|
$push: { members: { $each: missingAccounts, $position: 0 } }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const dmId =
|
||||||
|
direct?._id ??
|
||||||
|
(await client.createDoc(chunter.class.DirectMessage, core.space.Space, {
|
||||||
|
name: '',
|
||||||
|
description: '',
|
||||||
|
private: true,
|
||||||
|
archived: false,
|
||||||
|
members: accIds
|
||||||
|
}))
|
||||||
|
|
||||||
|
const context = await client.findOne(notification.class.DocNotifyContext, {
|
||||||
|
user: me._id,
|
||||||
|
objectId: dmId,
|
||||||
|
objectClass: chunter.class.DirectMessage
|
||||||
|
})
|
||||||
|
|
||||||
|
if (context !== undefined) {
|
||||||
|
if (context.hidden) {
|
||||||
|
await client.updateDoc(context._class, context.space, context._id, { hidden: false })
|
||||||
|
}
|
||||||
|
|
||||||
|
return dmId
|
||||||
|
}
|
||||||
|
|
||||||
|
const space = await client.findOne(
|
||||||
|
contact.class.PersonSpace,
|
||||||
|
{ person: me.person as Ref<Person> },
|
||||||
|
{ projection: { _id: 1 } }
|
||||||
|
)
|
||||||
|
if (space == null) return dmId
|
||||||
|
await client.createDoc(notification.class.DocNotifyContext, space._id, {
|
||||||
|
user: me._id,
|
||||||
|
objectId: dmId,
|
||||||
|
objectClass: chunter.class.DirectMessage,
|
||||||
|
objectSpace: core.space.Space,
|
||||||
|
hidden: false,
|
||||||
|
isPinned: false
|
||||||
|
})
|
||||||
|
|
||||||
|
return dmId
|
||||||
|
}
|
||||||
|
@ -208,7 +208,8 @@ export default plugin(chunterId, {
|
|||||||
TranslateMessage: '' as IntlString,
|
TranslateMessage: '' as IntlString,
|
||||||
Translate: '' as IntlString,
|
Translate: '' as IntlString,
|
||||||
ShowOriginal: '' as IntlString,
|
ShowOriginal: '' as IntlString,
|
||||||
Translating: '' as IntlString
|
Translating: '' as IntlString,
|
||||||
|
StartConversation: '' as IntlString
|
||||||
},
|
},
|
||||||
ids: {
|
ids: {
|
||||||
DMNotification: '' as Ref<NotificationType>,
|
DMNotification: '' as Ref<NotificationType>,
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { type Space, type Class, type CollaborativeDoc, type Doc, type Ref } from '@hcengineering/core'
|
import { type Space, type Class, type CollaborativeDoc, type Doc, type Ref } from '@hcengineering/core'
|
||||||
import { IntlString, translate } from '@hcengineering/platform'
|
import { IntlString, translate } from '@hcengineering/platform'
|
||||||
import { getFileUrl, getImageSize } from '@hcengineering/presentation'
|
import { getFileUrl, getImageSize, imageSizeToRatio } from '@hcengineering/presentation'
|
||||||
import { markupToJSON } from '@hcengineering/text'
|
import { markupToJSON } from '@hcengineering/text'
|
||||||
import {
|
import {
|
||||||
AnySvelteComponent,
|
AnySvelteComponent,
|
||||||
@ -297,7 +297,7 @@
|
|||||||
type: 'image',
|
type: 'image',
|
||||||
attrs: {
|
attrs: {
|
||||||
'file-id': attached.file,
|
'file-id': attached.file,
|
||||||
width: Math.round(size.width / size.pixelRatio)
|
width: imageSizeToRatio(size.width, size.pixelRatio)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Markup } from '@hcengineering/core'
|
import { Markup } from '@hcengineering/core'
|
||||||
import { IntlString } from '@hcengineering/platform'
|
import { IntlString } from '@hcengineering/platform'
|
||||||
import presentation, { MessageViewer, getFileUrl, getImageSize } from '@hcengineering/presentation'
|
import presentation, { MessageViewer, getFileUrl, getImageSize, imageSizeToRatio } from '@hcengineering/presentation'
|
||||||
import { EmptyMarkup } from '@hcengineering/text'
|
import { EmptyMarkup } from '@hcengineering/text'
|
||||||
import textEditor, { RefAction } from '@hcengineering/text-editor'
|
import textEditor, { RefAction } from '@hcengineering/text-editor'
|
||||||
import {
|
import {
|
||||||
@ -257,7 +257,7 @@
|
|||||||
type: 'image',
|
type: 'image',
|
||||||
attrs: {
|
attrs: {
|
||||||
'file-id': attached.file,
|
'file-id': attached.file,
|
||||||
width: Math.round(size.width / size.pixelRatio)
|
width: imageSizeToRatio(size.width, size.pixelRatio)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -153,12 +153,15 @@ export const ImageExtension = ImageNode.extend<ImageOptions>({
|
|||||||
const fileName = node.attrs.alt ?? ''
|
const fileName = node.attrs.alt ?? ''
|
||||||
const fileType = node.attrs['data-file-type'] ?? 'image/*'
|
const fileType = node.attrs['data-file-type'] ?? 'image/*'
|
||||||
|
|
||||||
|
const metadata = node.attrs.width !== undefined ? { originalWidth: node.attrs.width } : {}
|
||||||
|
|
||||||
showPopup(
|
showPopup(
|
||||||
FilePreviewPopup,
|
FilePreviewPopup,
|
||||||
{
|
{
|
||||||
file: fileId,
|
file: fileId,
|
||||||
name: fileName,
|
name: fileName,
|
||||||
contentType: fileType,
|
contentType: fileType,
|
||||||
|
metadata,
|
||||||
fullSize: true,
|
fullSize: true,
|
||||||
showIcon: false
|
showIcon: false
|
||||||
},
|
},
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
import { setPlatformStatus, unknownError } from '@hcengineering/platform'
|
import { setPlatformStatus, unknownError } from '@hcengineering/platform'
|
||||||
import { getImageSize } from '@hcengineering/presentation'
|
import { imageSizeToRatio, getImageSize } from '@hcengineering/presentation'
|
||||||
import { Extension } from '@tiptap/core'
|
import { Extension } from '@tiptap/core'
|
||||||
import { Plugin, PluginKey } from '@tiptap/pm/state'
|
import { Plugin, PluginKey } from '@tiptap/pm/state'
|
||||||
import { type EditorView } from '@tiptap/pm/view'
|
import { type EditorView } from '@tiptap/pm/view'
|
||||||
@ -41,6 +41,8 @@ function getType (type: string): 'image' | 'other' {
|
|||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
export const ImageUploadExtension = Extension.create<ImageUploadOptions>({
|
export const ImageUploadExtension = Extension.create<ImageUploadOptions>({
|
||||||
|
name: 'image-upload',
|
||||||
|
|
||||||
addOptions () {
|
addOptions () {
|
||||||
return {
|
return {
|
||||||
getFileUrl: () => ''
|
getFileUrl: () => ''
|
||||||
@ -157,7 +159,7 @@ async function handleImageUpload (
|
|||||||
src: url,
|
src: url,
|
||||||
alt: file.name,
|
alt: file.name,
|
||||||
title: file.name,
|
title: file.name,
|
||||||
width: Math.round(size.width / size.pixelRatio)
|
width: imageSizeToRatio(size.width, size.pixelRatio)
|
||||||
})
|
})
|
||||||
|
|
||||||
const transaction = view.state.tr.insert(pos?.pos ?? 0, node)
|
const transaction = view.state.tr.insert(pos?.pos ?? 0, node)
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { type Blob, type Ref } from '@hcengineering/core'
|
import { type Blob, type Ref } from '@hcengineering/core'
|
||||||
import { getBlobRef, type BlobMetadata } from '@hcengineering/presentation'
|
import { getBlobRef, imageSizeToRatio, type BlobMetadata } from '@hcengineering/presentation'
|
||||||
import { Loading } from '@hcengineering/ui'
|
import { Loading } from '@hcengineering/ui'
|
||||||
|
|
||||||
export let value: Ref<Blob>
|
export let value: Ref<Blob>
|
||||||
@ -22,15 +22,20 @@
|
|||||||
export let metadata: BlobMetadata | undefined
|
export let metadata: BlobMetadata | undefined
|
||||||
export let fit: boolean = false
|
export let fit: boolean = false
|
||||||
|
|
||||||
$: p = getBlobRef(value, name)
|
$: originalWidth = metadata?.originalWidth
|
||||||
$: width = metadata?.originalWidth ? `min(${metadata.originalWidth / metadata?.pixelRatio ?? 1}px, 100%)` : '100%'
|
$: originalHeight = metadata?.originalHeight
|
||||||
$: height = metadata?.originalHeight
|
$: pixelRatio = metadata?.pixelRatio ?? 1
|
||||||
? `min(${metadata.originalHeight / metadata?.pixelRatio ?? 1}px, ${fit ? '100%' : '80vh'})`
|
|
||||||
: '100%'
|
$: imageWidth = originalWidth != null ? imageSizeToRatio(originalWidth, pixelRatio) : undefined
|
||||||
|
$: imageHeight = originalHeight != null ? imageSizeToRatio(originalHeight, pixelRatio) : undefined
|
||||||
|
|
||||||
|
$: width = imageWidth != null ? `min(${imageWidth}px, 100%)` : '100%'
|
||||||
|
$: height = imageHeight != null ? `min(${imageHeight}px, ${fit ? '100%' : '80vh'})` : '100%'
|
||||||
|
|
||||||
let loading = true
|
let loading = true
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#await p then blobRef}
|
{#await getBlobRef(value, name) then blobRef}
|
||||||
{#if loading}
|
{#if loading}
|
||||||
<div class="flex-center w-full h-full clear-mins">
|
<div class="flex-center w-full h-full clear-mins">
|
||||||
<Loading />
|
<Loading />
|
||||||
|
@ -23,4 +23,4 @@
|
|||||||
export let selected: Ref<Widget> | undefined = undefined
|
export let selected: Ref<Widget> | undefined = undefined
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<WidgetsBar {widgets} {preferences} {selected} />
|
<WidgetsBar {widgets} {preferences} {selected} roundBorder />
|
||||||
|
@ -24,6 +24,7 @@
|
|||||||
export let widgets: Widget[] = []
|
export let widgets: Widget[] = []
|
||||||
export let preferences: WidgetPreference[] = []
|
export let preferences: WidgetPreference[] = []
|
||||||
export let selected: Ref<Widget> | undefined = undefined
|
export let selected: Ref<Widget> | undefined = undefined
|
||||||
|
export let roundBorder = false
|
||||||
|
|
||||||
function handleAddWidget (): void {
|
function handleAddWidget (): void {
|
||||||
showPopup(AddWidgetsPopup, { widgets })
|
showPopup(AddWidgetsPopup, { widgets })
|
||||||
@ -48,7 +49,7 @@
|
|||||||
.filter((widget): widget is Widget => widget !== undefined && widget.type === WidgetType.Configurable)
|
.filter((widget): widget is Widget => widget !== undefined && widget.type === WidgetType.Configurable)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="root">
|
<div class="root" class:roundBorder>
|
||||||
<div class="block">
|
<div class="block">
|
||||||
{#each fixedWidgets as widget}
|
{#each fixedWidgets as widget}
|
||||||
<WidgetPresenter
|
<WidgetPresenter
|
||||||
@ -103,6 +104,10 @@
|
|||||||
max-width: 3.5rem;
|
max-width: 3.5rem;
|
||||||
border-top: 1px solid var(--theme-divider-color);
|
border-top: 1px solid var(--theme-divider-color);
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
|
||||||
|
&.roundBorder {
|
||||||
|
border-top-left-radius: var(--small-focus-BorderRadius);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.block {
|
.block {
|
||||||
|
Loading…
Reference in New Issue
Block a user