mirror of
https://github.com/hcengineering/platform.git
synced 2026-09-05 17:27:41 +02:00
Merge remote-tracking branch 'origin/develop'
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
Vendored
+1
@@ -100,6 +100,7 @@
|
||||
"ACCOUNTS_URL": "http://localhost:3000",
|
||||
"UPLOAD_URL": "/files",
|
||||
"SERVER_PORT": "8087",
|
||||
"VERSION": null,
|
||||
"COLLABORATOR_URL": "ws://localhost:3078",
|
||||
"COLLABORATOR_API_URL": "http://localhost:3078",
|
||||
"CALENDAR_URL": "http://localhost:8095",
|
||||
|
||||
@@ -1 +1 @@
|
||||
"0.6.270"
|
||||
"0.6.271"
|
||||
@@ -77,6 +77,7 @@ services:
|
||||
- ACCOUNTS_URL=http://account:3000
|
||||
- UPLOAD_URL=/files
|
||||
- MONGO_URL=mongodb://mongodb:27017?compressors=snappy
|
||||
- 'MONGO_OPTIONS={"appName":"collaborator","maxPoolSize":2}'
|
||||
- STORAGE_CONFIG=${STORAGE_CONFIG}
|
||||
restart: unless-stopped
|
||||
front:
|
||||
@@ -95,6 +96,7 @@ services:
|
||||
- SERVER_PORT=8080
|
||||
- SERVER_SECRET=secret
|
||||
- MONGO_URL=mongodb://mongodb:27017?compressors=snappy
|
||||
- 'MONGO_OPTIONS={"appName":"front","maxPoolSize":1}'
|
||||
- ACCOUNTS_URL=http://localhost:3000
|
||||
- UPLOAD_URL=/files
|
||||
- ELASTIC_URL=http://elastic:9200
|
||||
@@ -135,6 +137,7 @@ services:
|
||||
- ENABLE_COMPRESSION=true
|
||||
- ELASTIC_URL=http://elastic:9200
|
||||
- MONGO_URL=mongodb://mongodb:27017?compressors=snappy
|
||||
- 'MONGO_OPTIONS={"appName": "transactor", "maxPoolSize": 1}'
|
||||
- METRICS_CONSOLE=false
|
||||
- METRICS_FILE=metrics.txt
|
||||
- STORAGE_CONFIG=${STORAGE_CONFIG}
|
||||
@@ -165,6 +168,7 @@ services:
|
||||
environment:
|
||||
- SECRET=secret
|
||||
- MONGO_URL=mongodb://mongodb:27017?compressors=snappy
|
||||
- 'MONGO_OPTIONS={"appName":"print","maxPoolSize":1}'
|
||||
- STORAGE_CONFIG=${STORAGE_CONFIG}
|
||||
deploy:
|
||||
resources:
|
||||
@@ -181,6 +185,7 @@ services:
|
||||
environment:
|
||||
- SECRET=secret
|
||||
- MONGO_URL=mongodb://mongodb:27017
|
||||
- 'MONGO_OPTIONS={"appName":"sign","maxPoolSize":1}'
|
||||
- MINIO_ENDPOINT=minio
|
||||
- MINIO_ACCESS_KEY=minioadmin
|
||||
- ACCOUNTS_URL=http://account:3000
|
||||
@@ -201,6 +206,7 @@ services:
|
||||
- SECRET=secret
|
||||
- PORT=4007
|
||||
- MONGO_URL=mongodb://mongodb:27017
|
||||
- 'MONGO_OPTIONS={"appName":"analytics","maxPoolSize":1}'
|
||||
- SERVICE_ID=analytics-collector-service
|
||||
- ACCOUNTS_URL=http://account:3000
|
||||
- SUPPORT_WORKSPACE=support
|
||||
|
||||
@@ -15,6 +15,6 @@
|
||||
"SIGN_URL": "http://localhost:4006",
|
||||
"ANALYTICS_COLLECTOR_URL": "http://localhost:4077",
|
||||
"BRANDING_URL": "/branding.json",
|
||||
"VERSION": "0.6.266",
|
||||
"MODEL_VERSION": "0.6.266"
|
||||
"VERSION": null,
|
||||
"MODEL_VERSION": null
|
||||
}
|
||||
@@ -70,6 +70,7 @@ export class TSpace extends TDoc implements Space {
|
||||
archived!: boolean
|
||||
|
||||
@Prop(ArrOf(TypeRef(core.class.Account)), core.string.Members)
|
||||
@Index(IndexKind.Indexed)
|
||||
members!: Arr<Ref<Account>>
|
||||
|
||||
@Prop(ArrOf(TypeRef(core.class.Account)), core.string.Owners)
|
||||
|
||||
@@ -39,6 +39,6 @@ export function createModel (builder: Builder): void {
|
||||
|
||||
builder.createDoc(core.class.DomainIndexConfiguration, core.space.Model, {
|
||||
domain: DOMAIN_PREFERENCE,
|
||||
disabled: [{ modifiedOn: 1 }, { createdOn: 1 }]
|
||||
disabled: [{ modifiedOn: 1 }, { createdOn: 1 }, { attachedTo: 1 }, { createdOn: -1 }, { modifiedBy: 1 }]
|
||||
})
|
||||
}
|
||||
|
||||
@@ -148,6 +148,7 @@ export function createModel (builder: Builder): void {
|
||||
builder.createDoc(core.class.DomainIndexConfiguration, core.space.Model, {
|
||||
domain: DOMAIN_TAGS,
|
||||
disabled: [
|
||||
{ _class: 1 },
|
||||
{ modifiedOn: 1 },
|
||||
{ modifiedBy: 1 },
|
||||
{ createdBy: 1 },
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// Basic performance metrics suite.
|
||||
|
||||
import { MetricsData } from '.'
|
||||
import { cutObjectArray } from '../utils'
|
||||
import { FullParamsType, Metrics, ParamsType } from './types'
|
||||
|
||||
/**
|
||||
@@ -35,7 +34,7 @@ function getUpdatedTopResult (
|
||||
|
||||
const newValue = {
|
||||
value: time,
|
||||
params: cutObjectArray(params)
|
||||
params
|
||||
}
|
||||
|
||||
if (result.length > 6) {
|
||||
|
||||
@@ -487,9 +487,13 @@ export class ApplyOperations extends TxOperations {
|
||||
extraNotify
|
||||
)
|
||||
)) as Promise<TxApplyResult>)
|
||||
const dnow = Date.now()
|
||||
if (typeof window === 'object' && window !== null) {
|
||||
console.log(`measure ${this.measureName}`, dnow - st, 'server time', result.serverTime)
|
||||
}
|
||||
return {
|
||||
result: result.success,
|
||||
time: Date.now() - st,
|
||||
time: dnow - st,
|
||||
serverTime: result.serverTime
|
||||
}
|
||||
}
|
||||
|
||||
+14
-10
@@ -13,8 +13,9 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { getEmbeddedLabel, IntlString } from '@hcengineering/platform'
|
||||
import { getEmbeddedLabel, IntlString, PlatformError, unknownError } from '@hcengineering/platform'
|
||||
import { deepEqual } from 'fast-equals'
|
||||
import { DOMAIN_BENCHMARK } from './benchmark'
|
||||
import {
|
||||
Account,
|
||||
AccountRole,
|
||||
@@ -46,7 +47,6 @@ import { TxOperations } from './operations'
|
||||
import { isPredicate } from './predicate'
|
||||
import { DocumentQuery, FindResult } from './storage'
|
||||
import { DOMAIN_TX } from './tx'
|
||||
import { DOMAIN_BENCHMARK } from './benchmark'
|
||||
|
||||
function toHex (value: number, chars: number): string {
|
||||
const result = value.toString(16)
|
||||
@@ -355,7 +355,6 @@ export class DocManager<T extends Doc> implements IDocManager<T> {
|
||||
|
||||
export class RateLimiter {
|
||||
idCounter: number = 0
|
||||
processingQueue = new Map<number, Promise<void>>()
|
||||
last: number = 0
|
||||
rate: number
|
||||
|
||||
@@ -366,21 +365,21 @@ export class RateLimiter {
|
||||
}
|
||||
|
||||
notify: (() => void)[] = []
|
||||
finished: boolean = false
|
||||
|
||||
async exec<T, B extends Record<string, any> = any>(op: (args?: B) => Promise<T>, args?: B): Promise<T> {
|
||||
const processingId = this.idCounter++
|
||||
|
||||
while (this.processingQueue.size >= this.rate) {
|
||||
if (this.finished) {
|
||||
throw new PlatformError(unknownError('No Possible to add/exec on finished queue'))
|
||||
}
|
||||
while (this.notify.length >= this.rate) {
|
||||
await new Promise<void>((resolve) => {
|
||||
this.notify.push(resolve)
|
||||
})
|
||||
}
|
||||
try {
|
||||
const p = op(args)
|
||||
this.processingQueue.set(processingId, p as Promise<void>)
|
||||
return await p
|
||||
} finally {
|
||||
this.processingQueue.delete(processingId)
|
||||
const n = this.notify.shift()
|
||||
if (n !== undefined) {
|
||||
n()
|
||||
@@ -389,7 +388,7 @@ export class RateLimiter {
|
||||
}
|
||||
|
||||
async add<T, B extends Record<string, any> = any>(op: (args?: B) => Promise<T>, args?: B): Promise<void> {
|
||||
if (this.processingQueue.size < this.rate) {
|
||||
if (this.notify.length < this.rate) {
|
||||
void this.exec(op, args)
|
||||
} else {
|
||||
await this.exec(op, args)
|
||||
@@ -397,7 +396,12 @@ export class RateLimiter {
|
||||
}
|
||||
|
||||
async waitProcessing (): Promise<void> {
|
||||
await Promise.all(this.processingQueue.values())
|
||||
this.finished = true
|
||||
while (this.notify.length > 0) {
|
||||
await new Promise<void>((resolve) => {
|
||||
this.notify.push(resolve)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
"temp/**",
|
||||
".build/**",
|
||||
"coverage/**",
|
||||
"**/*.svelte",
|
||||
".build/**",
|
||||
".validate/**",
|
||||
".format/**",
|
||||
|
||||
@@ -31,8 +31,8 @@ export type Loader = (locale: string) => Promise<Record<string, string | Record<
|
||||
type Messages = Record<string, IntlString | Record<string, IntlString>>
|
||||
|
||||
const loaders = new Map<Plugin, Loader>()
|
||||
const translations = new Map<Plugin, Messages | Status>()
|
||||
const cache = new Map<IntlString, IntlMessageFormat | Status>()
|
||||
const translations = new Map<string, Map<Plugin, Messages | Status>>()
|
||||
const cache = new Map<string, Map<IntlString, IntlMessageFormat | Status>>()
|
||||
const englishTranslationsForMissing = new Map<Plugin, Messages | Status>()
|
||||
/**
|
||||
* @public
|
||||
@@ -52,10 +52,14 @@ export async function loadPluginStrings (locale: string, force: boolean = false)
|
||||
cache.clear()
|
||||
}
|
||||
for (const [plugin] of loaders) {
|
||||
let messages = translations.get(plugin)
|
||||
const localtTanslations = translations.get(locale) ?? new Map<Plugin, Messages | Status<any>>()
|
||||
if (!translations.has(locale)) {
|
||||
translations.set(locale, localtTanslations)
|
||||
}
|
||||
let messages = localtTanslations.get(plugin)
|
||||
if (messages === undefined || force) {
|
||||
messages = await loadTranslationsForComponent(plugin, locale)
|
||||
translations.set(plugin, messages)
|
||||
localtTanslations.set(plugin, messages)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -83,10 +87,14 @@ async function loadTranslationsForComponent (plugin: Plugin, locale: string): Pr
|
||||
|
||||
async function getTranslation (id: _IdInfo, locale: string): Promise<IntlString | Status | undefined> {
|
||||
try {
|
||||
let messages = translations.get(id.component)
|
||||
const localtTanslations = translations.get(locale) ?? new Map<Plugin, Messages | Status<any>>()
|
||||
if (!translations.has(locale)) {
|
||||
translations.set(locale, localtTanslations)
|
||||
}
|
||||
let messages = localtTanslations.get(id.component)
|
||||
if (messages === undefined) {
|
||||
messages = await loadTranslationsForComponent(id.component, locale)
|
||||
translations.set(id.component, messages)
|
||||
localtTanslations.set(id.component, messages)
|
||||
}
|
||||
if (messages instanceof Status) {
|
||||
return messages
|
||||
@@ -127,7 +135,11 @@ export async function translate<P extends Record<string, any>> (
|
||||
language?: string
|
||||
): Promise<string> {
|
||||
const locale = language ?? getMetadata(platform.metadata.locale) ?? 'en'
|
||||
const compiled = cache.get(message)
|
||||
const localCache = cache.get(locale) ?? new Map<IntlString, IntlMessageFormat | Status>()
|
||||
if (!cache.has(locale)) {
|
||||
cache.set(locale, localCache)
|
||||
}
|
||||
const compiled = localCache.get(message)
|
||||
|
||||
if (compiled !== undefined) {
|
||||
if (compiled instanceof Status) {
|
||||
@@ -142,16 +154,16 @@ export async function translate<P extends Record<string, any>> (
|
||||
}
|
||||
const translation = (await getTranslation(id, locale)) ?? message
|
||||
if (translation instanceof Status) {
|
||||
cache.set(message, translation)
|
||||
localCache.set(message, translation)
|
||||
return message
|
||||
}
|
||||
const compiled = new IntlMessageFormat(translation, locale, undefined, { ignoreTag: true })
|
||||
cache.set(message, compiled)
|
||||
localCache.set(message, compiled)
|
||||
return compiled.format(params)
|
||||
} catch (err) {
|
||||
const status = unknownError(err)
|
||||
await setPlatformStatus(status)
|
||||
cache.set(message, status)
|
||||
localCache.set(message, status)
|
||||
return message
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,6 +190,7 @@
|
||||
{size}
|
||||
icon={IconAdd}
|
||||
showTooltip={{ label: create.label }}
|
||||
dataId={'btnAdd'}
|
||||
on:click={onCreate}
|
||||
disabled={readonly || loading}
|
||||
/>
|
||||
|
||||
@@ -13,18 +13,18 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { type Blob } from '@hcengineering/core'
|
||||
import { type Blob, type Ref } from '@hcengineering/core'
|
||||
import { Button, Component, Label, resizeObserver, deviceOptionsStore as deviceInfo } from '@hcengineering/ui'
|
||||
|
||||
import presentation from '../plugin'
|
||||
|
||||
import { getPreviewType, previewTypes } from '../file'
|
||||
import { BlobMetadata, FilePreviewExtension } from '../types'
|
||||
import { getFileUrl } from '../utils'
|
||||
|
||||
import { getBlobSrcFor } from '../preview'
|
||||
|
||||
export let file: Blob
|
||||
export let file: Ref<Blob>
|
||||
export let name: string
|
||||
export let contentType: string
|
||||
export let metadata: BlobMetadata | undefined
|
||||
export let props: Record<string, any> = {}
|
||||
export let fit: boolean = false
|
||||
@@ -35,7 +35,7 @@
|
||||
$: parentHeight = ($deviceInfo.docHeight * 80) / 100
|
||||
|
||||
let previewType: FilePreviewExtension | undefined = undefined
|
||||
$: void getPreviewType(file.contentType, $previewTypes).then((res) => {
|
||||
$: void getPreviewType(contentType, $previewTypes).then((res) => {
|
||||
previewType = res
|
||||
})
|
||||
|
||||
@@ -75,7 +75,7 @@
|
||||
}
|
||||
$: updateHeight(parentWidth, parentHeight, previewType, metadata)
|
||||
$: audio = previewType && Array.isArray(previewType) && previewType[0].contentType === 'audio/*'
|
||||
$: srcRef = getBlobSrcFor(file, name)
|
||||
$: srcRef = getFileUrl(file, name)
|
||||
</script>
|
||||
|
||||
<div
|
||||
@@ -90,10 +90,7 @@
|
||||
<Label label={presentation.string.FailedToPreview} />
|
||||
</div>
|
||||
{:else if previewType !== undefined}
|
||||
<Component
|
||||
is={previewType.component}
|
||||
props={{ value: file, name, contentType: file.contentType, metadata, ...props, fit }}
|
||||
/>
|
||||
<Component is={previewType.component} props={{ value: file, name, contentType, metadata, ...props, fit }} />
|
||||
{:else}
|
||||
<div class="flex-col items-center flex-gap-3">
|
||||
<Label label={presentation.string.ContentTypeNotSupported} />
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import core, { type Blob, type Ref } from '@hcengineering/core'
|
||||
import { type Blob, type Ref } from '@hcengineering/core'
|
||||
import { getEmbeddedLabel } from '@hcengineering/platform'
|
||||
import { Button, Dialog, tooltip } from '@hcengineering/ui'
|
||||
import { createEventDispatcher, onMount } from 'svelte'
|
||||
@@ -21,15 +21,15 @@
|
||||
import presentation from '../plugin'
|
||||
|
||||
import { BlobMetadata } from '../types'
|
||||
import { getClient } from '../utils'
|
||||
import { getBlobSrcFor } from '../preview'
|
||||
import { getClient, getFileUrl } from '../utils'
|
||||
|
||||
import ActionContext from './ActionContext.svelte'
|
||||
import FilePreview from './FilePreview.svelte'
|
||||
import Download from './icons/Download.svelte'
|
||||
|
||||
export let file: Blob | Ref<Blob> | undefined
|
||||
export let file: Ref<Blob> | undefined
|
||||
export let name: string
|
||||
export let contentType: string
|
||||
export let metadata: BlobMetadata | undefined
|
||||
export let props: Record<string, any> = {}
|
||||
|
||||
@@ -53,14 +53,7 @@
|
||||
return ext.substring(0, 4).toUpperCase()
|
||||
}
|
||||
|
||||
let blob: Blob | undefined = undefined
|
||||
$: void fetchBlob(file)
|
||||
|
||||
async function fetchBlob (file: Blob | Ref<Blob> | undefined): Promise<void> {
|
||||
blob = typeof file === 'string' ? await client.findOne(core.class.Blob, { _id: file }) : file
|
||||
}
|
||||
|
||||
$: srcRef = getBlobSrcFor(blob, name)
|
||||
$: srcRef = file !== undefined ? getFileUrl(file, name) : undefined
|
||||
</script>
|
||||
|
||||
<ActionContext context={{ mode: 'browser' }} />
|
||||
@@ -101,8 +94,8 @@
|
||||
{/await}
|
||||
</svelte:fragment>
|
||||
|
||||
{#if blob !== undefined}
|
||||
<FilePreview file={blob} {name} {metadata} {props} fit />
|
||||
{#if file}
|
||||
<FilePreview {file} {contentType} {name} {metadata} {props} fit />
|
||||
{/if}
|
||||
</Dialog>
|
||||
|
||||
|
||||
@@ -17,11 +17,11 @@
|
||||
import type { Blob, Ref } from '@hcengineering/core'
|
||||
import { Button, Dialog, Label, Spinner } from '@hcengineering/ui'
|
||||
import { createEventDispatcher, onMount } from 'svelte'
|
||||
import presentation, { getBlobSrcFor } from '..'
|
||||
import presentation, { getFileUrl } from '..'
|
||||
import ActionContext from './ActionContext.svelte'
|
||||
import Download from './icons/Download.svelte'
|
||||
|
||||
export let file: Blob | Ref<Blob> | undefined
|
||||
export let file: Ref<Blob> | undefined
|
||||
export let name: string
|
||||
export let contentType: string | undefined
|
||||
// export let popupOptions: PopupOptions
|
||||
@@ -45,7 +45,7 @@
|
||||
})
|
||||
let download: HTMLAnchorElement
|
||||
|
||||
$: srcRef = getBlobSrcFor(file, name)
|
||||
$: srcRef = file !== undefined ? getFileUrl(file, name) : undefined
|
||||
|
||||
$: isImage = contentType !== undefined && contentType.startsWith('image/')
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
type FindResult,
|
||||
type Hierarchy,
|
||||
type ModelDb,
|
||||
type QuerySelector,
|
||||
type Ref,
|
||||
type SearchOptions,
|
||||
type SearchQuery,
|
||||
@@ -330,6 +331,22 @@ export class OptimizeQueryMiddleware extends BasePresentationMiddleware implemen
|
||||
const fQuery = { ...query }
|
||||
const fOptions = { ...options }
|
||||
this.optimizeQuery<T>(fQuery, fOptions)
|
||||
|
||||
// Immidiate response queries, if have some $in with empty list.
|
||||
|
||||
for (const [k, v] of Object.entries(fQuery)) {
|
||||
if (typeof v === 'object' && v != null) {
|
||||
const vobj = v as QuerySelector<any>
|
||||
if (vobj.$in != null && vobj.$in.length === 0) {
|
||||
// Emopty in, will always return []
|
||||
return toFindResult([], 0)
|
||||
} else if (vobj.$in != null && vobj.$in.length === 1 && Object.keys(vobj).length === 1) {
|
||||
;(fQuery as any)[k] = vobj.$in[0]
|
||||
} else if (vobj.$nin != null && vobj.$nin.length === 1 && Object.keys(vobj).length === 1) {
|
||||
;(fQuery as any)[k] = { $ne: vobj.$nin[0] }
|
||||
}
|
||||
}
|
||||
}
|
||||
return await this.provideFindAll(_class, fQuery, fOptions)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,99 +1,36 @@
|
||||
import type { Blob, BlobLookup, Ref } from '@hcengineering/core'
|
||||
import core, { concatLink } from '@hcengineering/core'
|
||||
import type { Blob, Ref } from '@hcengineering/core'
|
||||
import { concatLink } from '@hcengineering/core'
|
||||
import { getMetadata } from '@hcengineering/platform'
|
||||
import { getBlobHref, getClient, getCurrentWorkspaceUrl, getFileUrl } from '.'
|
||||
import { getCurrentWorkspaceUrl, getFileUrl } from '.'
|
||||
import presentation from './plugin'
|
||||
|
||||
export interface ProviderPreviewConfig {
|
||||
// Identifier of provider
|
||||
// If set to '' could be applied to any provider, for example to exclude some 'image/gif' etc from being processing with providers.
|
||||
providerId: string
|
||||
// Preview url
|
||||
// If '' preview is disabled for config.
|
||||
previewUrl: string
|
||||
|
||||
// Content type markers, will check by containts, if passed, only allow to be used with matched content types.
|
||||
contentTypes?: string[]
|
||||
}
|
||||
|
||||
export interface PreviewConfig {
|
||||
default?: ProviderPreviewConfig
|
||||
previewers: Record<string, ProviderPreviewConfig[]>
|
||||
previewUrl: string
|
||||
}
|
||||
|
||||
const defaultPreview = (): ProviderPreviewConfig => ({
|
||||
providerId: '',
|
||||
previewUrl: `/files/${getCurrentWorkspaceUrl()}?file=:blobId&size=:size`
|
||||
})
|
||||
const defaultPreview = (): string => `/files/${getCurrentWorkspaceUrl()}?file=:blobId&size=:size`
|
||||
|
||||
/**
|
||||
*
|
||||
* PREVIEW_CONFIG env variable format.
|
||||
* A `;` separated list of triples, providerName|previewUrl|supportedFormats.
|
||||
|
||||
- providerName - a provider name should be same as in Storage configuration.
|
||||
It coult be empty and it will match by content types.
|
||||
- previewUrl - an Url with :workspace, :blobId, :downloadFile, :size placeholders, they will be replaced in UI with an appropriate blob values.
|
||||
- supportedFormats - a `,` separated list of file extensions.
|
||||
- contentTypes - a ',' separated list of content type patterns.
|
||||
|
||||
* previewUrl - an Url with :workspace, :blobId, :downloadFile, :size placeholders, they will be replaced in UI with an appropriate blob values.
|
||||
*/
|
||||
export function parsePreviewConfig (config?: string): PreviewConfig | undefined {
|
||||
if (config === undefined) {
|
||||
return
|
||||
}
|
||||
const result: PreviewConfig = { previewers: {} }
|
||||
const nolineData = config
|
||||
.split('\n')
|
||||
.map((it) => it.trim())
|
||||
.join(';')
|
||||
const configs = nolineData.split(';')
|
||||
for (const c of configs) {
|
||||
if (c === '') {
|
||||
continue // Skip empty lines
|
||||
}
|
||||
const [provider, url, contentTypes] = c.split('|').map((it) => it.trim())
|
||||
const p: ProviderPreviewConfig = {
|
||||
providerId: provider,
|
||||
previewUrl: url,
|
||||
// Allow preview only for images by default
|
||||
contentTypes:
|
||||
contentTypes !== undefined
|
||||
? contentTypes
|
||||
.split(',')
|
||||
.map((it) => it.trim())
|
||||
.filter((it) => it !== '')
|
||||
: ['image/']
|
||||
}
|
||||
|
||||
if (provider === '*') {
|
||||
result.default = p
|
||||
} else {
|
||||
result.previewers[provider] = [...(result.previewers[provider] ?? []), p]
|
||||
}
|
||||
}
|
||||
return result
|
||||
return { previewUrl: config }
|
||||
}
|
||||
|
||||
export function getPreviewConfig (): PreviewConfig {
|
||||
return (
|
||||
(getMetadata(presentation.metadata.PreviewConfig) as PreviewConfig) ?? {
|
||||
default: defaultPreview(),
|
||||
previewers: {
|
||||
'': [
|
||||
{
|
||||
providerId: '',
|
||||
contentTypes: ['image/gif', 'image/apng', 'image/svg'], // Disable gif and apng format preview.
|
||||
previewUrl: ''
|
||||
}
|
||||
]
|
||||
}
|
||||
previewUrl: defaultPreview()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
export async function getBlobRef (
|
||||
blob: Blob | undefined,
|
||||
file: Ref<Blob>,
|
||||
name?: string,
|
||||
width?: number
|
||||
@@ -101,74 +38,30 @@ export async function getBlobRef (
|
||||
src: string
|
||||
srcset: string
|
||||
}> {
|
||||
let _blob = blob as BlobLookup
|
||||
if (_blob === undefined) {
|
||||
_blob = (await getClient().findOne(core.class.Blob, { _id: file })) as BlobLookup
|
||||
}
|
||||
return {
|
||||
src: _blob?.downloadUrl ?? getFileUrl(file, name),
|
||||
srcset: _blob !== undefined ? getSrcSet(_blob, width) : ''
|
||||
src: getFileUrl(file, name),
|
||||
srcset: getSrcSet(file, width)
|
||||
}
|
||||
}
|
||||
|
||||
export async function getBlobSrcSet (_blob: Blob | undefined, file: Ref<Blob>, width?: number): Promise<string> {
|
||||
if (_blob === undefined) {
|
||||
_blob = await getClient().findOne(core.class.Blob, { _id: file })
|
||||
}
|
||||
return _blob !== undefined ? getSrcSet(_blob, width) : ''
|
||||
export async function getBlobSrcSet (file: Ref<Blob>, width?: number): Promise<string> {
|
||||
return getSrcSet(file, width)
|
||||
}
|
||||
|
||||
/**
|
||||
* Select content provider based on content type.
|
||||
*/
|
||||
export function selectProvider (
|
||||
blob: Blob,
|
||||
providers: Array<ProviderPreviewConfig | undefined>
|
||||
): ProviderPreviewConfig | undefined {
|
||||
const isMatched = (it: ProviderPreviewConfig): boolean =>
|
||||
it.contentTypes === undefined || it.contentTypes.some((e) => blob.contentType === e || blob.contentType.includes(e))
|
||||
|
||||
let candidate: ProviderPreviewConfig | undefined
|
||||
for (const p of providers) {
|
||||
if (p !== undefined && isMatched(p)) {
|
||||
if (p.previewUrl === '') {
|
||||
// we found one disable config line, so return it.
|
||||
return p
|
||||
}
|
||||
candidate = p
|
||||
}
|
||||
}
|
||||
|
||||
return candidate
|
||||
export function getSrcSet (_blob: Ref<Blob>, width?: number): string {
|
||||
return blobToSrcSet(getPreviewConfig(), _blob, width)
|
||||
}
|
||||
|
||||
export function getSrcSet (_blob: Blob, width?: number): string {
|
||||
const blob = _blob as BlobLookup
|
||||
const c = getPreviewConfig()
|
||||
|
||||
// Select providers from
|
||||
const cfg = selectProvider(blob, [...(c.previewers[_blob.provider] ?? []), ...(c.previewers[''] ?? []), c.default])
|
||||
if (cfg === undefined || cfg.previewUrl === '') {
|
||||
return '' // No previewer is available for blob
|
||||
}
|
||||
|
||||
return blobToSrcSet(cfg, blob, width)
|
||||
}
|
||||
|
||||
function blobToSrcSet (
|
||||
cfg: ProviderPreviewConfig,
|
||||
blob: { _id: Ref<Blob>, downloadUrl?: string },
|
||||
width: number | undefined
|
||||
): string {
|
||||
function blobToSrcSet (cfg: PreviewConfig, blob: Ref<Blob>, width: number | undefined): string {
|
||||
let url = cfg.previewUrl.replaceAll(':workspace', encodeURIComponent(getCurrentWorkspaceUrl()))
|
||||
const downloadUrl = blob.downloadUrl ?? getFileUrl(blob._id)
|
||||
const downloadUrl = getFileUrl(blob)
|
||||
|
||||
const frontUrl = getMetadata(presentation.metadata.FrontUrl) ?? window.location.origin
|
||||
if (!url.includes('://')) {
|
||||
url = concatLink(frontUrl ?? '', url)
|
||||
}
|
||||
url = url.replaceAll(':downloadFile', encodeURIComponent(downloadUrl))
|
||||
url = url.replaceAll(':blobId', encodeURIComponent(blob._id))
|
||||
url = url.replaceAll(':blobId', encodeURIComponent(blob))
|
||||
|
||||
let result = ''
|
||||
const fu = url
|
||||
@@ -187,24 +80,9 @@ function blobToSrcSet (
|
||||
return result
|
||||
}
|
||||
|
||||
export async function getBlobSrcFor (blob: Blob | Ref<Blob> | undefined, name?: string): Promise<string> {
|
||||
return blob === undefined
|
||||
? ''
|
||||
: typeof blob === 'string'
|
||||
? await getBlobHref(undefined, blob, name)
|
||||
: await getBlobHref(blob, blob._id)
|
||||
}
|
||||
|
||||
/***
|
||||
* @deprecated, please use Blob direct operations.
|
||||
*/
|
||||
export function getFileSrcSet (_blob: Ref<Blob>, width?: number): string {
|
||||
const cfg = getPreviewConfig()
|
||||
return blobToSrcSet(
|
||||
cfg.default ?? defaultPreview(),
|
||||
{
|
||||
_id: _blob
|
||||
},
|
||||
width
|
||||
)
|
||||
return blobToSrcSet(getPreviewConfig(), _blob, width)
|
||||
}
|
||||
|
||||
@@ -24,7 +24,6 @@ import core, {
|
||||
type AnyAttribute,
|
||||
type ArrOf,
|
||||
type AttachedDoc,
|
||||
type BlobLookup,
|
||||
type Class,
|
||||
type Client,
|
||||
type Collection,
|
||||
@@ -450,18 +449,6 @@ export function createQuery (dontDestroy?: boolean): LiveQuery {
|
||||
return new LiveQuery(dontDestroy)
|
||||
}
|
||||
|
||||
export async function getBlobHref (
|
||||
_blob: PlatformBlob | undefined,
|
||||
file: Ref<PlatformBlob>,
|
||||
filename?: string
|
||||
): Promise<string> {
|
||||
let blob = _blob as BlobLookup
|
||||
if (blob?.downloadUrl === undefined) {
|
||||
blob = (await getClient().findOne(core.class.Blob, { _id: file })) as BlobLookup
|
||||
}
|
||||
return blob?.downloadUrl ?? getFileUrl(file, filename)
|
||||
}
|
||||
|
||||
export function getCurrentWorkspaceUrl (): string {
|
||||
const wsId = get(workspaceId)
|
||||
if (wsId == null) {
|
||||
@@ -700,7 +687,11 @@ export function isAdminUser (): boolean {
|
||||
}
|
||||
|
||||
export function isSpace (space: Doc): space is Space {
|
||||
return getClient().getHierarchy().isDerived(space._class, core.class.Space)
|
||||
return isSpaceClass(space._class)
|
||||
}
|
||||
|
||||
export function isSpaceClass (_class: Ref<Class<Doc>>): boolean {
|
||||
return getClient().getHierarchy().isDerived(_class, core.class.Space)
|
||||
}
|
||||
|
||||
export function setPresentationCookie (token: string, workspaceId: string): void {
|
||||
|
||||
@@ -13,17 +13,7 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import {
|
||||
type Blob,
|
||||
type Branding,
|
||||
type DocumentUpdate,
|
||||
type MeasureContext,
|
||||
type Ref,
|
||||
type StorageIterator,
|
||||
type WorkspaceId,
|
||||
type WorkspaceIdWithUrl
|
||||
} from '@hcengineering/core'
|
||||
import type { BlobLookup } from '@hcengineering/core/src/classes'
|
||||
import { type Blob, type MeasureContext, type StorageIterator, type WorkspaceId } from '@hcengineering/core'
|
||||
import { type Readable } from 'stream'
|
||||
|
||||
export type ListBlobResult = Omit<Blob, 'contentType' | 'version'>
|
||||
@@ -38,11 +28,6 @@ export interface BlobStorageIterator {
|
||||
close: () => Promise<void>
|
||||
}
|
||||
|
||||
export interface BlobLookupResult {
|
||||
lookups: BlobLookup[]
|
||||
updates?: Map<Ref<Blob>, DocumentUpdate<BlobLookup>>
|
||||
}
|
||||
|
||||
export interface BucketInfo {
|
||||
name: string
|
||||
delete: () => Promise<void>
|
||||
@@ -50,11 +35,6 @@ export interface BucketInfo {
|
||||
}
|
||||
|
||||
export interface StorageAdapter {
|
||||
// If specified will limit a blobs available to put into selected provider.
|
||||
// A set of content type patterns supported by this storage provider.
|
||||
// If not defined, will be suited for any other content types.
|
||||
contentTypes?: string[]
|
||||
|
||||
initialize: (ctx: MeasureContext, workspaceId: WorkspaceId) => Promise<void>
|
||||
|
||||
close: () => Promise<void>
|
||||
@@ -84,14 +64,6 @@ export interface StorageAdapter {
|
||||
offset: number,
|
||||
length?: number
|
||||
) => Promise<Readable>
|
||||
|
||||
// Lookup will extend Blob with lookup information.
|
||||
lookup: (
|
||||
ctx: MeasureContext,
|
||||
workspaceId: WorkspaceIdWithUrl,
|
||||
branding: Branding | null,
|
||||
docs: Blob[]
|
||||
) => Promise<BlobLookupResult>
|
||||
}
|
||||
|
||||
export interface StorageAdapterEx extends StorageAdapter {
|
||||
@@ -189,15 +161,6 @@ export class DummyStorageAdapter implements StorageAdapter, StorageAdapterEx {
|
||||
): Promise<UploadedObjectInfo> {
|
||||
throw new Error('not implemented')
|
||||
}
|
||||
|
||||
async lookup (
|
||||
ctx: MeasureContext,
|
||||
workspaceId: WorkspaceIdWithUrl,
|
||||
branding: Branding | null,
|
||||
docs: Blob[]
|
||||
): Promise<BlobLookupResult> {
|
||||
return { lookups: [] }
|
||||
}
|
||||
}
|
||||
|
||||
export function createDummyStorageAdapter (): StorageAdapter {
|
||||
|
||||
@@ -28,6 +28,10 @@ export function tooltip (node: HTMLElement, options?: LabelAndProps): any {
|
||||
if (options === undefined) {
|
||||
return {}
|
||||
}
|
||||
if (options.label === undefined && options.component === undefined) {
|
||||
// No tooltip
|
||||
return {}
|
||||
}
|
||||
let opt = options
|
||||
const show = (): void => {
|
||||
const shown = !!(storedValue.label !== undefined || storedValue.component !== undefined)
|
||||
@@ -113,7 +117,7 @@ export function showTooltip (
|
||||
props,
|
||||
anchor,
|
||||
onUpdate,
|
||||
kind,
|
||||
kind: kind ?? 'tooltip',
|
||||
keys,
|
||||
type: 'tooltip'
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import activity, { type ActivityMessage, type SavedMessage } from '@hcengineering/activity'
|
||||
import { type Ref, SortingOrder, type WithLookup } from '@hcengineering/core'
|
||||
import core, { type Ref, SortingOrder, type WithLookup } from '@hcengineering/core'
|
||||
import { writable } from 'svelte/store'
|
||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||
|
||||
@@ -14,7 +14,7 @@ export function loadSavedMessages (): void {
|
||||
if (client !== undefined) {
|
||||
savedMessagesQuery.query(
|
||||
activity.class.SavedMessage,
|
||||
{},
|
||||
{ space: core.space.Workspace },
|
||||
(res) => {
|
||||
savedMessagesStore.set(res.filter(({ $lookup }) => $lookup?.attachedTo !== undefined))
|
||||
},
|
||||
|
||||
@@ -13,20 +13,26 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import activity, { ActivityExtension, ActivityMessage, DisplayActivityMessage } from '@hcengineering/activity'
|
||||
import activity, {
|
||||
ActivityExtension,
|
||||
ActivityMessage,
|
||||
ActivityReference,
|
||||
DisplayActivityMessage,
|
||||
WithReferences
|
||||
} from '@hcengineering/activity'
|
||||
import { Doc, Ref, SortingOrder } from '@hcengineering/core'
|
||||
import { createQuery, getClient, isSpace } from '@hcengineering/presentation'
|
||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||
import { Grid, Label, Spinner, location, Lazy } from '@hcengineering/ui'
|
||||
import { onDestroy, onMount } from 'svelte'
|
||||
|
||||
import ActivityExtensionComponent from './ActivityExtension.svelte'
|
||||
import ActivityFilter from './ActivityFilter.svelte'
|
||||
import { combineActivityMessages } from '../activityMessagesUtils'
|
||||
import { canGroupMessages, getMessageFromLoc } from '../utils'
|
||||
import { combineActivityMessages, sortActivityMessages } from '../activityMessagesUtils'
|
||||
import { canGroupMessages, getMessageFromLoc, getSpace } from '../utils'
|
||||
import ActivityMessagePresenter from './activity-message/ActivityMessagePresenter.svelte'
|
||||
import { messageInFocus } from '../activity'
|
||||
|
||||
export let object: Doc
|
||||
export let object: WithReferences<Doc>
|
||||
export let showCommenInput: boolean = true
|
||||
export let transparent: boolean = false
|
||||
export let focusIndex: number = -1
|
||||
@@ -34,12 +40,17 @@
|
||||
|
||||
const client = getClient()
|
||||
const activityMessagesQuery = createQuery()
|
||||
const refsQuery = createQuery()
|
||||
|
||||
let extensions: ActivityExtension[] = []
|
||||
|
||||
let filteredMessages: DisplayActivityMessage[] = []
|
||||
let activityMessages: ActivityMessage[] = []
|
||||
let isLoading = false
|
||||
let allMessages: ActivityMessage[] = []
|
||||
let messages: ActivityMessage[] = []
|
||||
let refs: ActivityReference[] = []
|
||||
|
||||
let isMessagesLoading = false
|
||||
let isRefsLoading = true
|
||||
|
||||
let activityBox: HTMLElement | undefined
|
||||
let selectedMessageId: Ref<ActivityMessage> | undefined = undefined
|
||||
@@ -163,16 +174,38 @@
|
||||
extensions = res
|
||||
})
|
||||
|
||||
// Load references from other spaces separately because they can have any different spaces
|
||||
$: if ((object.references ?? 0) > 0) {
|
||||
refsQuery.query(
|
||||
activity.class.ActivityReference,
|
||||
{ attachedTo: object._id, space: { $ne: getSpace(object) } },
|
||||
(res) => {
|
||||
refs = res
|
||||
isRefsLoading = false
|
||||
},
|
||||
{
|
||||
sort: {
|
||||
createdOn: SortingOrder.Ascending
|
||||
}
|
||||
}
|
||||
)
|
||||
} else {
|
||||
isRefsLoading = false
|
||||
refsQuery.unsubscribe()
|
||||
}
|
||||
|
||||
$: allMessages = sortActivityMessages(messages.concat(refs))
|
||||
|
||||
async function updateActivityMessages (objectId: Ref<Doc>, order: SortingOrder): Promise<void> {
|
||||
isLoading = true
|
||||
isMessagesLoading = true
|
||||
|
||||
const res = activityMessagesQuery.query(
|
||||
activity.class.ActivityMessage,
|
||||
{ attachedTo: objectId, space: isSpace(object) ? object._id : object.space },
|
||||
{ attachedTo: objectId, space: getSpace(object) },
|
||||
(result: ActivityMessage[]) => {
|
||||
void combineActivityMessages(result, order).then((messages) => {
|
||||
activityMessages = messages
|
||||
isLoading = false
|
||||
void combineActivityMessages(result, order).then((res) => {
|
||||
messages = res
|
||||
isMessagesLoading = false
|
||||
})
|
||||
},
|
||||
{
|
||||
@@ -182,10 +215,11 @@
|
||||
}
|
||||
)
|
||||
if (!res) {
|
||||
isLoading = false
|
||||
isMessagesLoading = false
|
||||
}
|
||||
}
|
||||
|
||||
$: isLoading = isMessagesLoading || isRefsLoading
|
||||
$: areMessagesLoaded = !isLoading && filteredMessages.length > 0
|
||||
|
||||
$: if (activityBox && areMessagesLoaded) {
|
||||
@@ -206,7 +240,7 @@
|
||||
{/if}
|
||||
</span>
|
||||
<ActivityFilter
|
||||
messages={activityMessages}
|
||||
messages={allMessages}
|
||||
{object}
|
||||
on:update={(e) => {
|
||||
filteredMessages = e.detail
|
||||
|
||||
+31
-41
@@ -16,8 +16,8 @@
|
||||
import { Label, tooltip } from '@hcengineering/ui'
|
||||
import { DocNotifyContext } from '@hcengineering/notification'
|
||||
import activity, { ActivityMessage } from '@hcengineering/activity'
|
||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||
import { Doc, Ref } from '@hcengineering/core'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import { Doc } from '@hcengineering/core'
|
||||
import { getDocLinkTitle, getDocTitle, ObjectIcon } from '@hcengineering/view-resources'
|
||||
import { getEmbeddedLabel } from '@hcengineering/platform'
|
||||
import contact from '@hcengineering/contact'
|
||||
@@ -25,54 +25,44 @@
|
||||
import ActivityMessagePreview from './ActivityMessagePreview.svelte'
|
||||
|
||||
export let context: DocNotifyContext
|
||||
export let object: ActivityMessage
|
||||
|
||||
const client = getClient()
|
||||
const hierarchy = client.getHierarchy()
|
||||
const parentQuery = createQuery()
|
||||
|
||||
let parentMessage: ActivityMessage | undefined = undefined
|
||||
let title: string | undefined = undefined
|
||||
let object: Doc | undefined = undefined
|
||||
|
||||
$: parentQuery.query(activity.class.ActivityMessage, { _id: context.attachedTo as Ref<ActivityMessage> }, (res) => {
|
||||
parentMessage = res[0]
|
||||
})
|
||||
|
||||
$: parentMessage &&
|
||||
client.findOne(parentMessage.attachedToClass, { _id: parentMessage.attachedTo }).then((res) => {
|
||||
object = res
|
||||
})
|
||||
let doc: Doc | undefined = undefined
|
||||
|
||||
$: object &&
|
||||
getDocLinkTitle(client, object._id, object._class, object).then((res) => {
|
||||
client.findOne(object.attachedToClass, { _id: object.attachedTo, space: object.space }).then((res) => {
|
||||
doc = res
|
||||
})
|
||||
|
||||
$: doc &&
|
||||
getDocLinkTitle(client, doc._id, doc._class, doc).then((res) => {
|
||||
title = res
|
||||
})
|
||||
</script>
|
||||
|
||||
{#if parentMessage}
|
||||
<span class="flex-presenter flex-gap-1 font-semi-bold">
|
||||
<Label label={(parentMessage?.replies ?? 0) > 0 ? activity.string.Thread : activity.string.Message} />
|
||||
{#if title}
|
||||
<span class="lower">
|
||||
<Label label={activity.string.In} />
|
||||
</span>
|
||||
{#if object}
|
||||
{#await getDocTitle(client, object._id, object._class, object) then tooltipLabel}
|
||||
<span
|
||||
class="flex-presenter flex-gap-0-5"
|
||||
use:tooltip={tooltipLabel ? { label: getEmbeddedLabel(tooltipLabel) } : undefined}
|
||||
>
|
||||
<ObjectIcon
|
||||
value={object}
|
||||
size={hierarchy.isDerived(object._class, contact.class.Person) ? 'tiny' : 'small'}
|
||||
/>
|
||||
{title}
|
||||
</span>
|
||||
{/await}
|
||||
{/if}
|
||||
<span class="flex-presenter flex-gap-1 font-semi-bold">
|
||||
<Label label={(object?.replies ?? 0) > 0 ? activity.string.Thread : activity.string.Message} />
|
||||
{#if title}
|
||||
<span class="lower">
|
||||
<Label label={activity.string.In} />
|
||||
</span>
|
||||
{#if doc}
|
||||
{#await getDocTitle(client, doc._id, doc._class, doc) then tooltipLabel}
|
||||
<span
|
||||
class="flex-presenter flex-gap-0-5"
|
||||
use:tooltip={tooltipLabel ? { label: getEmbeddedLabel(tooltipLabel) } : undefined}
|
||||
>
|
||||
<ObjectIcon value={doc} size={hierarchy.isDerived(doc._class, contact.class.Person) ? 'tiny' : 'small'} />
|
||||
{title}
|
||||
</span>
|
||||
{/await}
|
||||
{/if}
|
||||
</span>
|
||||
<span class="font-normal">
|
||||
<ActivityMessagePreview value={parentMessage} readonly type="content-only" />
|
||||
</span>
|
||||
{/if}
|
||||
{/if}
|
||||
</span>
|
||||
<span class="font-normal">
|
||||
<ActivityMessagePreview value={object} {doc} readonly type="content-only" />
|
||||
</span>
|
||||
|
||||
+3
-1
@@ -21,6 +21,7 @@
|
||||
|
||||
import activity from '../../plugin'
|
||||
|
||||
export let doc: Doc | undefined
|
||||
export let value: DisplayActivityMessage
|
||||
export let readonly = false
|
||||
export let type: ActivityMessagePreviewType = 'full'
|
||||
@@ -44,7 +45,8 @@
|
||||
type,
|
||||
readonly,
|
||||
actions,
|
||||
space
|
||||
space,
|
||||
doc
|
||||
}}
|
||||
on:click
|
||||
/>
|
||||
|
||||
+2
-14
@@ -13,22 +13,10 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { createQuery, MessageViewer } from '@hcengineering/presentation'
|
||||
import { Ref } from '@hcengineering/core'
|
||||
import activity, { ActivityReference } from '@hcengineering/activity'
|
||||
import { MessageViewer } from '@hcengineering/presentation'
|
||||
import { ActivityReference } from '@hcengineering/activity'
|
||||
|
||||
export let _id: Ref<ActivityReference> | undefined = undefined
|
||||
export let value: ActivityReference | undefined = undefined
|
||||
|
||||
const query = createQuery()
|
||||
|
||||
$: if (value === undefined && _id !== undefined) {
|
||||
query.query(activity.class.ActivityReference, { _id }, (res) => {
|
||||
value = res.shift()
|
||||
})
|
||||
} else {
|
||||
query.unsubscribe()
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if value}
|
||||
|
||||
+4
-4
@@ -16,12 +16,12 @@
|
||||
import type { Doc } from '@hcengineering/core'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import { DocReferencePresenter } from '@hcengineering/view-resources'
|
||||
import activity from '../../plugin'
|
||||
|
||||
import { isActivityMessage } from '../../activityMessagesUtils'
|
||||
import view from '@hcengineering/view'
|
||||
import { Icon, Label } from '@hcengineering/ui'
|
||||
|
||||
import activity from '../../plugin'
|
||||
import { isActivityMessage } from '../../activityMessagesUtils'
|
||||
|
||||
export let value: Doc | undefined
|
||||
|
||||
const client = getClient()
|
||||
@@ -31,7 +31,7 @@
|
||||
$: showParent = isActivityMessage(value)
|
||||
|
||||
$: isActivityMessage(value) &&
|
||||
client.findOne(value.attachedToClass, { _id: value.attachedTo }).then((res) => {
|
||||
client.findOne(value.attachedToClass, { _id: value.attachedTo, space: value.space }).then((res) => {
|
||||
parentObject = res
|
||||
})
|
||||
</script>
|
||||
|
||||
+9
-4
@@ -88,13 +88,17 @@
|
||||
attributeModel = model
|
||||
})
|
||||
|
||||
async function getParentMessage (_class: Ref<Class<Doc>>, _id: Ref<Doc>): Promise<ActivityMessage | undefined> {
|
||||
async function getParentMessage (
|
||||
_class: Ref<Class<Doc>>,
|
||||
_id: Ref<Doc>,
|
||||
space: Ref<Space>
|
||||
): Promise<ActivityMessage | undefined> {
|
||||
if (hierarchy.isDerived(_class, activity.class.ActivityMessage)) {
|
||||
return await client.findOne(activity.class.ActivityMessage, { _id: _id as Ref<ActivityMessage> })
|
||||
return await client.findOne(activity.class.ActivityMessage, { _id: _id as Ref<ActivityMessage>, space })
|
||||
}
|
||||
}
|
||||
|
||||
$: void getParentMessage(value.attachedToClass, value.attachedTo).then((res) => {
|
||||
$: void getParentMessage(value.attachedToClass, value.attachedTo, value.space).then((res) => {
|
||||
parentMessage = res as DisplayActivityMessage
|
||||
})
|
||||
|
||||
@@ -150,6 +154,7 @@
|
||||
|
||||
const _id = parentMessage ? parentMessage.attachedTo : message.attachedTo
|
||||
const _class = parentMessage ? parentMessage.attachedToClass : message.attachedToClass
|
||||
const space = parentMessage ? parentMessage.space : message.space
|
||||
|
||||
if (doc !== undefined && doc._id === _id) {
|
||||
parentObject = doc
|
||||
@@ -163,7 +168,7 @@
|
||||
return
|
||||
}
|
||||
|
||||
parentObjectQuery.query(_class, { _id }, (res) => {
|
||||
parentObjectQuery.query(_class, { _id, space }, (res) => {
|
||||
parentObject = res[0]
|
||||
})
|
||||
}
|
||||
|
||||
+9
-3
@@ -32,6 +32,7 @@
|
||||
import DocUpdateMessageContent from './DocUpdateMessageContent.svelte'
|
||||
import DocUpdateMessageAttributes from './DocUpdateMessageAttributes.svelte'
|
||||
|
||||
export let doc: Doc | undefined
|
||||
export let value: DisplayDocUpdateMessage
|
||||
export let readonly = false
|
||||
export let type: ActivityMessagePreviewType = 'full'
|
||||
@@ -65,15 +66,20 @@
|
||||
attributeModel = model
|
||||
})
|
||||
|
||||
$: viewlet?.component && loadObject(value.objectId, value.objectClass)
|
||||
$: viewlet?.component && loadObject(value.objectId, value.objectClass, value.space, doc)
|
||||
|
||||
async function loadObject (_id: Ref<Doc>, _class: Ref<Class<Doc>>, space: Ref<Space>, doc?: Doc): Promise<void> {
|
||||
if (doc?._id === _id) {
|
||||
object = doc
|
||||
return
|
||||
}
|
||||
|
||||
async function loadObject (_id: Ref<Doc>, _class: Ref<Class<Doc>>): Promise<void> {
|
||||
const isObjectRemoved = await checkIsObjectRemoved(client, _id, _class)
|
||||
|
||||
if (isObjectRemoved) {
|
||||
object = await buildRemovedDoc(client, _id, _class)
|
||||
} else {
|
||||
objectQuery.query(_class, { _id }, (res) => {
|
||||
objectQuery.query(_class, { _id, space }, (res) => {
|
||||
object = res[0]
|
||||
})
|
||||
}
|
||||
|
||||
@@ -14,18 +14,19 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { Reaction } from '@hcengineering/activity'
|
||||
import { Ref } from '@hcengineering/core'
|
||||
import { Ref, Space } from '@hcengineering/core'
|
||||
import { createQuery } from '@hcengineering/presentation'
|
||||
|
||||
import activity from '../../plugin'
|
||||
|
||||
export let _id: Ref<Reaction>
|
||||
export let space: Ref<Space>
|
||||
export let value: Reaction | undefined = undefined
|
||||
|
||||
const query = createQuery()
|
||||
|
||||
$: value === undefined &&
|
||||
query.query(activity.class.Reaction, { _id }, (res) => {
|
||||
query.query(activity.class.Reaction, { _id, space }, (res) => {
|
||||
value = res[0]
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
import activity, { ActivityMessage, Reaction } from '@hcengineering/activity'
|
||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||
|
||||
import { updateDocReactions } from '../../utils'
|
||||
import { getSpace, updateDocReactions } from '../../utils'
|
||||
import Reactions from './Reactions.svelte'
|
||||
|
||||
export let object: ActivityMessage | undefined
|
||||
@@ -30,9 +30,13 @@
|
||||
$: hasReactions = object?.reactions && object.reactions > 0
|
||||
|
||||
$: if (object && hasReactions) {
|
||||
reactionsQuery.query(activity.class.Reaction, { attachedTo: object._id }, (res: Reaction[]) => {
|
||||
reactions = res
|
||||
})
|
||||
reactionsQuery.query(
|
||||
activity.class.Reaction,
|
||||
{ attachedTo: object._id, space: getSpace(object) },
|
||||
(res: Reaction[]) => {
|
||||
reactions = res
|
||||
}
|
||||
)
|
||||
} else {
|
||||
reactionsQuery.unsubscribe()
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
$: if (message && hasReactions) {
|
||||
reactionsQuery.query(
|
||||
activity.class.Reaction,
|
||||
{ attachedTo: message._id },
|
||||
{ attachedTo: message._id, space: message.space },
|
||||
(res: Reaction[]) => {
|
||||
reactions = res
|
||||
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
import type { ActivityMessage, Reaction } from '@hcengineering/activity'
|
||||
import core, { getCurrentAccount, isOtherHour, type Doc, type Ref, type TxOperations } from '@hcengineering/core'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import core, {
|
||||
getCurrentAccount,
|
||||
isOtherHour,
|
||||
type Doc,
|
||||
type Ref,
|
||||
type TxOperations,
|
||||
type Space
|
||||
} from '@hcengineering/core'
|
||||
import { getClient, isSpace } from '@hcengineering/presentation'
|
||||
import {
|
||||
EmojiPopup,
|
||||
closePopup,
|
||||
@@ -58,7 +65,7 @@ export async function addReactionAction (
|
||||
const client = getClient()
|
||||
const reactions: Reaction[] =
|
||||
(message.reactions ?? 0) > 0
|
||||
? await client.findAll<Reaction>(activity.class.Reaction, { attachedTo: message._id })
|
||||
? await client.findAll<Reaction>(activity.class.Reaction, { attachedTo: message._id, space: message.space })
|
||||
: []
|
||||
const element = getEventPositionElement(ev)
|
||||
|
||||
@@ -169,3 +176,7 @@ export function shouldScrollToActivity (): boolean {
|
||||
const loc = getCurrentResolvedLocation()
|
||||
return getMessageFromLoc(loc) !== undefined
|
||||
}
|
||||
|
||||
export function getSpace (doc: Doc): Ref<Space> {
|
||||
return isSpace(doc) ? doc._id : doc.space
|
||||
}
|
||||
|
||||
@@ -235,6 +235,10 @@ export interface UserMentionInfo extends AttachedDoc {
|
||||
content: string
|
||||
}
|
||||
|
||||
export type WithReferences<T extends Doc> = T & {
|
||||
references?: number
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
import {
|
||||
FilePreviewPopup,
|
||||
canPreviewFile,
|
||||
getBlobHref,
|
||||
getFileUrl,
|
||||
getPreviewAlignment,
|
||||
previewTypes
|
||||
} from '@hcengineering/presentation'
|
||||
@@ -71,7 +71,8 @@
|
||||
showPopup(
|
||||
FilePreviewPopup,
|
||||
{
|
||||
file: attachment.$lookup?.file ?? attachment.file,
|
||||
file: attachment.file,
|
||||
contentType: attachment.type,
|
||||
name: attachment.name,
|
||||
metadata: attachment.metadata
|
||||
},
|
||||
@@ -130,32 +131,30 @@
|
||||
</script>
|
||||
|
||||
<div class="flex">
|
||||
{#await getBlobHref(attachment.$lookup?.file, attachment.file, attachment.name) then href}
|
||||
<a
|
||||
class="mr-1 flex-row-center gap-2 p-1"
|
||||
{href}
|
||||
download={attachment.name}
|
||||
bind:this={download}
|
||||
use:tooltip={{ label: getEmbeddedLabel(attachment.name) }}
|
||||
on:click|stopPropagation
|
||||
>
|
||||
{#if canPreview}
|
||||
<ActionIcon
|
||||
icon={IconOpen}
|
||||
size={'medium'}
|
||||
action={(evt) => {
|
||||
showPreview(evt)
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
<a
|
||||
class="mr-1 flex-row-center gap-2 p-1"
|
||||
href={getFileUrl(attachment.file, attachment.name)}
|
||||
download={attachment.name}
|
||||
bind:this={download}
|
||||
use:tooltip={{ label: getEmbeddedLabel(attachment.name) }}
|
||||
on:click|stopPropagation
|
||||
>
|
||||
{#if canPreview}
|
||||
<ActionIcon
|
||||
icon={FileDownload}
|
||||
icon={IconOpen}
|
||||
size={'medium'}
|
||||
action={() => {
|
||||
download.click()
|
||||
action={(evt) => {
|
||||
showPreview(evt)
|
||||
}}
|
||||
/>
|
||||
</a>
|
||||
{/await}
|
||||
{/if}
|
||||
<ActionIcon
|
||||
icon={FileDownload}
|
||||
size={'medium'}
|
||||
action={() => {
|
||||
download.click()
|
||||
}}
|
||||
/>
|
||||
</a>
|
||||
<ActionIcon icon={IconMoreH} size={'medium'} action={showMenu} />
|
||||
</div>
|
||||
|
||||
@@ -48,11 +48,6 @@
|
||||
},
|
||||
(res) => {
|
||||
resAttachments = res
|
||||
},
|
||||
{
|
||||
lookup: {
|
||||
file: core.class.Blob
|
||||
}
|
||||
}
|
||||
)
|
||||
} else {
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<script lang="ts">
|
||||
import type { Attachment } from '@hcengineering/attachment'
|
||||
import type { WithLookup } from '@hcengineering/core'
|
||||
import { FilePreviewPopup, getBlobHref } from '@hcengineering/presentation'
|
||||
import { FilePreviewPopup, getFileUrl } from '@hcengineering/presentation'
|
||||
import { closeTooltip, showPopup } from '@hcengineering/ui'
|
||||
import filesize from 'filesize'
|
||||
import { getType } from '../utils'
|
||||
@@ -45,41 +45,27 @@
|
||||
showPopup(
|
||||
FilePreviewPopup,
|
||||
{
|
||||
file: value.$lookup?.file ?? value.file,
|
||||
file: value.file,
|
||||
contentType: value.type,
|
||||
name: value.name,
|
||||
metadata: value.metadata
|
||||
},
|
||||
isImage(value.type) ? 'centered' : 'float'
|
||||
)
|
||||
}
|
||||
$: src = getFileUrl(value.file, value.name)
|
||||
</script>
|
||||
|
||||
<div class="gridCellOverlay">
|
||||
{#await getBlobHref(value.$lookup?.file, value.file, value.name) then src}
|
||||
<div class="gridCell">
|
||||
{#if isImage(value.type)}
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<div class="cellImagePreview" on:click={openAttachment}>
|
||||
<img class={'img-fit'} {src} alt={value.name} />
|
||||
</div>
|
||||
{:else}
|
||||
<div class="cellMiscPreview">
|
||||
{#if isEmbedded(value.type)}
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<div class="flex-center extensionIcon" on:click={openAttachment}>
|
||||
{extensionIconLabel(value.name)}
|
||||
</div>
|
||||
{:else}
|
||||
<a class="no-line" href={src} download={value.name}>
|
||||
<div class="flex-center extensionIcon">{extensionIconLabel(value.name)}</div>
|
||||
</a>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="cellInfo">
|
||||
<div class="gridCell">
|
||||
{#if isImage(value.type)}
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<div class="cellImagePreview" on:click={openAttachment}>
|
||||
<img class={'img-fit'} {src} alt={value.name} />
|
||||
</div>
|
||||
{:else}
|
||||
<div class="cellMiscPreview">
|
||||
{#if isEmbedded(value.type)}
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
@@ -91,24 +77,38 @@
|
||||
<div class="flex-center extensionIcon">{extensionIconLabel(value.name)}</div>
|
||||
</a>
|
||||
{/if}
|
||||
<div class="eCellInfoData">
|
||||
{#if isEmbedded(value.type)}
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<div class="eCellInfoFilename" on:click={openAttachment}>
|
||||
{trimFilename(value.name)}
|
||||
</div>
|
||||
{:else}
|
||||
<div class="eCellInfoFilename">
|
||||
<a href={src} download={value.name}>{trimFilename(value.name)}</a>
|
||||
</div>
|
||||
{/if}
|
||||
<div class="eCellInfoFilesize">{filesize(value.size)}</div>
|
||||
</div>
|
||||
<div class="eCellInfoMenu"><slot name="rowMenu" /></div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="cellInfo">
|
||||
{#if isEmbedded(value.type)}
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<div class="flex-center extensionIcon" on:click={openAttachment}>
|
||||
{extensionIconLabel(value.name)}
|
||||
</div>
|
||||
{:else}
|
||||
<a class="no-line" href={src} download={value.name}>
|
||||
<div class="flex-center extensionIcon">{extensionIconLabel(value.name)}</div>
|
||||
</a>
|
||||
{/if}
|
||||
<div class="eCellInfoData">
|
||||
{#if isEmbedded(value.type)}
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<div class="eCellInfoFilename" on:click={openAttachment}>
|
||||
{trimFilename(value.name)}
|
||||
</div>
|
||||
{:else}
|
||||
<div class="eCellInfoFilename">
|
||||
<a href={src} download={value.name}>{trimFilename(value.name)}</a>
|
||||
</div>
|
||||
{/if}
|
||||
<div class="eCellInfoFilesize">{filesize(value.size)}</div>
|
||||
</div>
|
||||
<div class="eCellInfoMenu"><slot name="rowMenu" /></div>
|
||||
</div>
|
||||
{/await}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
@@ -107,7 +107,7 @@
|
||||
</script>
|
||||
|
||||
<div class="container" style="width:{toStyle(dimensions.width)}; height:{toStyle(dimensions.height)}">
|
||||
{#await getBlobRef(value.$lookup?.file, value.file, value.name, sizeToWidth(urlSize)) then blobSrc}
|
||||
{#await getBlobRef(value.file, value.name, sizeToWidth(urlSize)) then blobSrc}
|
||||
<img
|
||||
src={blobSrc.src}
|
||||
style:object-fit={getObjectFit(dimensions)}
|
||||
|
||||
@@ -43,11 +43,6 @@
|
||||
},
|
||||
(res) => {
|
||||
docs = res
|
||||
},
|
||||
{
|
||||
lookup: {
|
||||
file: core.class.Blob
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -83,7 +83,8 @@
|
||||
showPopup(
|
||||
FilePreviewPopup,
|
||||
{
|
||||
file: value.$lookup?.file ?? value.file,
|
||||
file: value.file,
|
||||
contentType: value.type,
|
||||
name: value.name,
|
||||
metadata: value.metadata
|
||||
},
|
||||
@@ -114,7 +115,7 @@
|
||||
{:else}
|
||||
<div class="flex-row-center attachment-container">
|
||||
{#if value}
|
||||
{#await getBlobRef(value.$lookup?.file, value.file, value.name, sizeToWidth('large')) then valueRef}
|
||||
{#await getBlobRef(value.file, value.name, sizeToWidth('large')) then valueRef}
|
||||
<a
|
||||
class="no-line"
|
||||
style:flex-shrink={0}
|
||||
|
||||
@@ -51,7 +51,7 @@
|
||||
if (listProvider !== undefined) listProvider.updateFocus(value)
|
||||
const popupInfo = showPopup(
|
||||
FilePreviewPopup,
|
||||
{ file: value.$lookup?.file ?? value.file, name: value.name },
|
||||
{ file: value.file, name: value.name, contentType: value.type },
|
||||
value.type.startsWith('image/') ? 'centered' : 'float'
|
||||
)
|
||||
dispatch('open', popupInfo.id)
|
||||
|
||||
@@ -121,11 +121,6 @@
|
||||
(res) => {
|
||||
originalAttachments = new Set(res.map((p) => p._id))
|
||||
attachments = toIdMap(res)
|
||||
},
|
||||
{
|
||||
lookup: {
|
||||
file: core.class.Blob
|
||||
}
|
||||
}
|
||||
)
|
||||
} else {
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<script lang="ts">
|
||||
import attachment, { Attachment, BlobMetadata } from '@hcengineering/attachment'
|
||||
import contact from '@hcengineering/contact'
|
||||
import core, { Account, Doc, Ref, generateId, type Blob } from '@hcengineering/core'
|
||||
import { Account, Doc, Ref, generateId, type Blob } from '@hcengineering/core'
|
||||
import { IntlString, getResource, setPlatformStatus, unknownError } from '@hcengineering/platform'
|
||||
import {
|
||||
FileOrBlob,
|
||||
@@ -25,7 +25,6 @@
|
||||
getFileMetadata,
|
||||
uploadFile
|
||||
} from '@hcengineering/presentation'
|
||||
import { getCollaborationUser, getObjectLinkFragment } from '@hcengineering/view-resources'
|
||||
import textEditor, { type RefAction, type TextEditorHandler } from '@hcengineering/text-editor'
|
||||
import {
|
||||
AttachIcon,
|
||||
@@ -38,6 +37,7 @@
|
||||
import { AnySvelteComponent, getEventPositionElement, getPopupPositionElement, navigate } from '@hcengineering/ui'
|
||||
import { uploadFiles } from '@hcengineering/uploader'
|
||||
import view from '@hcengineering/view'
|
||||
import { getCollaborationUser, getObjectLinkFragment } from '@hcengineering/view-resources'
|
||||
|
||||
import AttachmentsGrid from './AttachmentsGrid.svelte'
|
||||
|
||||
@@ -106,11 +106,6 @@
|
||||
},
|
||||
(res) => {
|
||||
attachments = res
|
||||
},
|
||||
{
|
||||
lookup: {
|
||||
file: core.class.Blob
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -13,23 +13,23 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher, onDestroy } from 'svelte'
|
||||
import { Attachment } from '@hcengineering/attachment'
|
||||
import core, { Account, Class, Doc, generateId, Markup, Ref, Space, toIdMap, type Blob } from '@hcengineering/core'
|
||||
import { Account, Class, Doc, generateId, Markup, Ref, Space, toIdMap, type Blob } from '@hcengineering/core'
|
||||
import { IntlString, setPlatformStatus, unknownError } from '@hcengineering/platform'
|
||||
import {
|
||||
createQuery,
|
||||
DraftController,
|
||||
deleteFile,
|
||||
DraftController,
|
||||
draftsStore,
|
||||
getClient,
|
||||
getFileMetadata,
|
||||
uploadFile
|
||||
} from '@hcengineering/presentation'
|
||||
import textEditor, { type RefAction } from '@hcengineering/text-editor'
|
||||
import { EmptyMarkup } from '@hcengineering/text'
|
||||
import textEditor, { type RefAction } from '@hcengineering/text-editor'
|
||||
import { AttachIcon, StyledTextBox } from '@hcengineering/text-editor-resources'
|
||||
import { ButtonSize } from '@hcengineering/ui'
|
||||
import { createEventDispatcher, onDestroy } from 'svelte'
|
||||
|
||||
import attachment from '../plugin'
|
||||
import AttachmentsGrid from './AttachmentsGrid.svelte'
|
||||
@@ -127,11 +127,6 @@
|
||||
originalAttachments = new Set(res.map((p) => p._id))
|
||||
attachments = toIdMap(res)
|
||||
dispatch('attach', { action: 'saved', value: attachments.size })
|
||||
},
|
||||
{
|
||||
lookup: {
|
||||
file: core.class.Blob
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@@ -14,9 +14,9 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
import type { Attachment } from '@hcengineering/attachment'
|
||||
import { getBlobHref } from '@hcengineering/presentation'
|
||||
|
||||
import type { WithLookup } from '@hcengineering/core'
|
||||
import { getFileUrl } from '@hcengineering/presentation'
|
||||
import AttachmentPresenter from './AttachmentPresenter.svelte'
|
||||
|
||||
export let value: WithLookup<Attachment>
|
||||
@@ -58,9 +58,7 @@
|
||||
</script>
|
||||
|
||||
<video controls width={dimensions.width} height={dimensions.height} preload={preload ? 'auto' : 'none'}>
|
||||
{#await getBlobHref(value.$lookup?.file, value.file, value.name) then href}
|
||||
<source src={href} />
|
||||
{/await}
|
||||
<source src={getFileUrl(value.file, value.name)} />
|
||||
<track kind="captions" label={value.name} />
|
||||
<div class="container">
|
||||
<AttachmentPresenter {value} />
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<script lang="ts">
|
||||
import attachment, { Attachment } from '@hcengineering/attachment'
|
||||
import { Doc, getCurrentAccount, type WithLookup } from '@hcengineering/core'
|
||||
import { getBlobHref, getClient } from '@hcengineering/presentation'
|
||||
import { getClient, getFileUrl } from '@hcengineering/presentation'
|
||||
import { Icon, IconMoreV, Menu, showPopup } from '@hcengineering/ui'
|
||||
import { AttachmentGalleryPresenter } from '..'
|
||||
import FileDownload from './icons/FileDownload.svelte'
|
||||
@@ -56,12 +56,11 @@
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<AttachmentGalleryPresenter value={attachment}>
|
||||
<svelte:fragment slot="rowMenu">
|
||||
{@const href = getFileUrl(attachment.file, attachment.name)}
|
||||
<div class="eAttachmentCellActions" class:fixed={i === selectedFileNumber}>
|
||||
{#await getBlobHref(attachment.$lookup?.file, attachment.file, attachment.name) then href}
|
||||
<a {href} download={attachment.name}>
|
||||
<Icon icon={FileDownload} size={'small'} />
|
||||
</a>
|
||||
{/await}
|
||||
<a {href} download={attachment.name}>
|
||||
<Icon icon={FileDownload} size={'small'} />
|
||||
</a>
|
||||
<div class="eAttachmentCellMenu" on:click={(event) => showFileMenu(event, attachment, i)}>
|
||||
<IconMoreV size={'small'} />
|
||||
</div>
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<script lang="ts">
|
||||
import attachment, { Attachment } from '@hcengineering/attachment'
|
||||
import { Doc, getCurrentAccount, type WithLookup } from '@hcengineering/core'
|
||||
import { getBlobHref, getClient } from '@hcengineering/presentation'
|
||||
import { getClient, getFileUrl } from '@hcengineering/presentation'
|
||||
import { Icon, IconMoreV, Menu, showPopup } from '@hcengineering/ui'
|
||||
import { AttachmentPresenter } from '..'
|
||||
import FileDownload from './icons/FileDownload.svelte'
|
||||
@@ -51,16 +51,15 @@
|
||||
|
||||
<div class="flex-col">
|
||||
{#each attachments as attachment, i}
|
||||
{@const href = getFileUrl(attachment.file, attachment.name)}
|
||||
<div class="flex-between attachmentRow" class:fixed={i === selectedFileNumber}>
|
||||
<div class="item flex">
|
||||
<AttachmentPresenter value={attachment} />
|
||||
</div>
|
||||
<div class="eAttachmentRowActions" class:fixed={i === selectedFileNumber}>
|
||||
{#await getBlobHref(attachment.$lookup?.file, attachment.file, attachment.name) then href}
|
||||
<a {href} download={attachment.name}>
|
||||
<Icon icon={FileDownload} size={'small'} />
|
||||
</a>
|
||||
{/await}
|
||||
<a {href} download={attachment.name}>
|
||||
<Icon icon={FileDownload} size={'small'} />
|
||||
</a>
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<div class="eAttachmentRowMenu" on:click={(event) => showFileMenu(event, attachment, i)}>
|
||||
|
||||
@@ -14,11 +14,11 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
import type { Attachment } from '@hcengineering/attachment'
|
||||
import { getBlobHref, getFileUrl } from '@hcengineering/presentation'
|
||||
import { CircleButton, Progress } from '@hcengineering/ui'
|
||||
import Play from './icons/Play.svelte'
|
||||
import Pause from './icons/Pause.svelte'
|
||||
import type { WithLookup } from '@hcengineering/core'
|
||||
import { getFileUrl } from '@hcengineering/presentation'
|
||||
import { CircleButton, Progress } from '@hcengineering/ui'
|
||||
import Pause from './icons/Pause.svelte'
|
||||
import Play from './icons/Play.svelte'
|
||||
|
||||
export let value: WithLookup<Attachment>
|
||||
export let fullSize = false
|
||||
@@ -48,9 +48,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<audio bind:duration bind:currentTime={time} bind:paused>
|
||||
{#await getBlobHref(value.$lookup?.file, value.file, value.name) then href}
|
||||
<source src={href} type={value.type} />
|
||||
{/await}
|
||||
<source src={getFileUrl(value.file, value.name)} type={value.type} />
|
||||
</audio>
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
@@ -92,10 +92,7 @@
|
||||
{ ...nameQuery, ...senderQuery, ...spaceQuery, ...dateQuery, ...fileTypeQuery },
|
||||
{
|
||||
sort: sortModeToOptionObject(selectedSort_),
|
||||
limit: 200,
|
||||
lookup: {
|
||||
file: core.class.Blob
|
||||
}
|
||||
limit: 200
|
||||
}
|
||||
)
|
||||
isLoading = false
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
// import { Doc } from '@hcengineering/core'
|
||||
import type { Attachment } from '@hcengineering/attachment'
|
||||
import type { WithLookup } from '@hcengineering/core'
|
||||
import presentation, { ActionContext, IconDownload, getBlobHref, getBlobRef } from '@hcengineering/presentation'
|
||||
import presentation, { ActionContext, getFileUrl, IconDownload } from '@hcengineering/presentation'
|
||||
import { Button, Dialog } from '@hcengineering/ui'
|
||||
import { createEventDispatcher, onMount } from 'svelte'
|
||||
import { getType } from '../utils'
|
||||
@@ -40,7 +40,7 @@
|
||||
})
|
||||
let download: HTMLAnchorElement
|
||||
$: type = getType(value.type)
|
||||
$: srcRef = getBlobHref(value.$lookup?.file, value.file, value.name)
|
||||
$: srcRef = getFileUrl(value.file, value.name)
|
||||
</script>
|
||||
|
||||
<ActionContext context={{ mode: 'browser' }} />
|
||||
|
||||
@@ -17,14 +17,7 @@
|
||||
import { Photo } from '@hcengineering/attachment'
|
||||
import { Class, Doc, Ref, Space, type WithLookup } from '@hcengineering/core'
|
||||
import { setPlatformStatus, unknownError } from '@hcengineering/platform'
|
||||
import {
|
||||
FilePreviewPopup,
|
||||
createQuery,
|
||||
getBlobHref,
|
||||
getClient,
|
||||
uploadFile,
|
||||
getBlobRef
|
||||
} from '@hcengineering/presentation'
|
||||
import { FilePreviewPopup, createQuery, getBlobRef, getClient, uploadFile } from '@hcengineering/presentation'
|
||||
import { Button, IconAdd, Label, Spinner, showPopup } from '@hcengineering/ui'
|
||||
import attachment from '../plugin'
|
||||
import UploadDuo from './icons/UploadDuo.svelte'
|
||||
@@ -99,7 +92,7 @@
|
||||
if (item !== undefined) {
|
||||
showPopup(
|
||||
FilePreviewPopup,
|
||||
{ file: item.$lookup?.file ?? item.file, name: item.name },
|
||||
{ file: item.file, name: item.name, contentType: item.type },
|
||||
item.type.startsWith('image/') ? 'centered' : 'float'
|
||||
)
|
||||
} else {
|
||||
@@ -157,7 +150,7 @@
|
||||
click(ev, image)
|
||||
}}
|
||||
>
|
||||
{#await getBlobRef(image.$lookup?.file, image.file, image.name) then blobRef}
|
||||
{#await getBlobRef(image.file, image.name) then blobRef}
|
||||
<img src={blobRef.src} srcset={blobRef.srcset} alt={image.name} />
|
||||
{/await}
|
||||
</div>
|
||||
|
||||
@@ -25,11 +25,10 @@ import {
|
||||
type Space,
|
||||
type Timestamp
|
||||
} from '@hcengineering/core'
|
||||
|
||||
import { derived, get, type Readable, writable } from 'svelte/store'
|
||||
import { type ActivityMessage } from '@hcengineering/activity'
|
||||
import activity, { type ActivityMessage, type ActivityReference } from '@hcengineering/activity'
|
||||
import attachment from '@hcengineering/attachment'
|
||||
import { combineActivityMessages } from '@hcengineering/activity-resources'
|
||||
import { combineActivityMessages, sortActivityMessages } from '@hcengineering/activity-resources'
|
||||
import { type ChatMessage } from '@hcengineering/chunter'
|
||||
import notification, { type DocNotifyContext } from '@hcengineering/notification'
|
||||
|
||||
@@ -70,6 +69,7 @@ export class ChannelDataProvider implements IChannelDataProvider {
|
||||
|
||||
private readonly metadataQuery = createQuery(true)
|
||||
private readonly tailQuery = createQuery(true)
|
||||
private readonly refsQuery = createQuery(true)
|
||||
|
||||
private chatId: Ref<Doc> | undefined = undefined
|
||||
private readonly msgClass: Ref<Class<ActivityMessage>>
|
||||
@@ -79,12 +79,14 @@ export class ChannelDataProvider implements IChannelDataProvider {
|
||||
public readonly metadataStore = writable<MessageMetadata[]>([])
|
||||
private readonly tailStore = writable<ActivityMessage[]>([])
|
||||
private readonly chunksStore = writable<Chunk[]>([])
|
||||
public readonly refsStore = writable<ActivityReference[]>([])
|
||||
|
||||
private readonly isInitialLoadingStore = writable(false)
|
||||
private readonly isInitialLoadedStore = writable(false)
|
||||
private readonly isTailLoading = writable(false)
|
||||
|
||||
readonly isTailLoaded = writable(false)
|
||||
readonly isRefsLoading = writable(false)
|
||||
|
||||
public datesStore = writable<Timestamp[]>([])
|
||||
public newTimestampStore = writable<Timestamp | undefined>(undefined)
|
||||
@@ -92,8 +94,8 @@ export class ChannelDataProvider implements IChannelDataProvider {
|
||||
public isLoadingMoreStore = writable(false)
|
||||
|
||||
public isLoadingStore = derived(
|
||||
[this.isInitialLoadedStore, this.isTailLoading],
|
||||
([initialLoaded, tailLoading]) => !initialLoaded || tailLoading
|
||||
[this.isInitialLoadedStore, this.isTailLoading, this.isRefsLoading],
|
||||
([initialLoaded, tailLoading, isRefsLoading]) => !initialLoaded || tailLoading || isRefsLoading
|
||||
)
|
||||
|
||||
private readonly backwardNextStore = writable<Chunk | undefined>(undefined)
|
||||
@@ -107,8 +109,9 @@ export class ChannelDataProvider implements IChannelDataProvider {
|
||||
|
||||
private nextChunkAdding = false
|
||||
|
||||
public messagesStore = derived([this.chunksStore, this.tailStore], ([chunks, tail]) => {
|
||||
return [...chunks.map(({ data }) => data).flat(), ...tail]
|
||||
public messagesStore = derived([this.chunksStore, this.tailStore, this.refsStore], ([chunks, tail, refs]) => {
|
||||
const data = chunks.map(({ data, to, from }) => mergeWithRefs(data, refs, from, to))
|
||||
return [...data.flat(), ...mergeWithRefs(tail, refs, tail[0]?.createdOn)]
|
||||
})
|
||||
|
||||
public canLoadNextForwardStore = derived([this.messagesStore, this.forwardNextStore], ([messages, forwardNext]) => {
|
||||
@@ -123,18 +126,20 @@ export class ChannelDataProvider implements IChannelDataProvider {
|
||||
chatId: Ref<Doc>,
|
||||
_class: Ref<Class<ActivityMessage>>,
|
||||
selectedMsgId: Ref<ActivityMessage> | undefined,
|
||||
loadAll = false
|
||||
loadAll = false,
|
||||
withRefs = false
|
||||
) {
|
||||
this.chatId = chatId
|
||||
this.msgClass = _class
|
||||
this.selectedMsgId = selectedMsgId
|
||||
void this.loadData(loadAll)
|
||||
void this.loadData(loadAll, withRefs)
|
||||
}
|
||||
|
||||
public destroy (): void {
|
||||
this.clearData()
|
||||
this.metadataQuery.unsubscribe()
|
||||
this.tailQuery.unsubscribe()
|
||||
this.refsQuery.unsubscribe()
|
||||
}
|
||||
|
||||
public canLoadMore (mode: LoadMode, timestamp?: Timestamp): boolean {
|
||||
@@ -168,11 +173,29 @@ export class ChannelDataProvider implements IChannelDataProvider {
|
||||
this.clearMessages()
|
||||
}
|
||||
|
||||
private async loadData (loadAll = false): Promise<void> {
|
||||
loadRefs (): void {
|
||||
// Load references from other spaces separately because they can have any different spaces
|
||||
this.refsQuery.query(
|
||||
activity.class.ActivityReference,
|
||||
{ attachedTo: this.chatId, space: { $ne: this.space } },
|
||||
(res) => {
|
||||
this.refsStore.set(res)
|
||||
this.isRefsLoading.set(false)
|
||||
},
|
||||
{ sort: { createdOn: SortingOrder.Ascending } }
|
||||
)
|
||||
}
|
||||
|
||||
private async loadData (loadAll = false, withRefs = false): Promise<void> {
|
||||
if (this.chatId === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
if (withRefs && this.msgClass === activity.class.ActivityMessage) {
|
||||
this.isRefsLoading.set(true)
|
||||
this.loadRefs()
|
||||
}
|
||||
|
||||
this.metadataQuery.query(
|
||||
this.msgClass,
|
||||
{ attachedTo: this.chatId, space: this.space },
|
||||
@@ -622,3 +645,21 @@ export class ChannelDataProvider implements IChannelDataProvider {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
function mergeWithRefs (
|
||||
messages: ActivityMessage[],
|
||||
refs: ActivityReference[],
|
||||
from?: Timestamp,
|
||||
to?: Timestamp
|
||||
): ActivityMessage[] {
|
||||
if (from === undefined) return messages
|
||||
if (refs.length === 0) return messages
|
||||
|
||||
const refsFiltered = refs.filter(
|
||||
({ createdOn }) => (createdOn ?? 0) >= from && (to === undefined || (createdOn ?? 0) <= to)
|
||||
)
|
||||
|
||||
if (refsFiltered.length === 0) return messages
|
||||
|
||||
return sortActivityMessages(messages.concat(refsFiltered))
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<script lang="ts">
|
||||
import { Class, Doc, getCurrentAccount, Ref } from '@hcengineering/core'
|
||||
import notification, { DocNotifyContext } from '@hcengineering/notification'
|
||||
import activity, { ActivityMessage, ActivityMessagesFilter } from '@hcengineering/activity'
|
||||
import activity, { ActivityMessage, ActivityMessagesFilter, WithReferences } from '@hcengineering/activity'
|
||||
import { getClient, isSpace } from '@hcengineering/presentation'
|
||||
import { getMessageFromLoc, messageInFocus } from '@hcengineering/activity-resources'
|
||||
import { location as locationStore } from '@hcengineering/ui'
|
||||
@@ -57,6 +57,8 @@
|
||||
dataProvider = undefined
|
||||
})
|
||||
|
||||
let refsLoaded = false
|
||||
|
||||
$: isDocChannel = !hierarchy.isDerived(object._class, chunter.class.ChunterSpace)
|
||||
$: _class = isDocChannel ? activity.class.ActivityMessage : chunter.class.ChatMessage
|
||||
$: collection = isDocChannel ? 'comments' : 'messages'
|
||||
@@ -78,11 +80,17 @@
|
||||
attachedTo: object._id,
|
||||
user: getCurrentAccount()._id
|
||||
}))
|
||||
|
||||
const hasRefs = ((object as WithReferences<Doc>).references ?? 0) > 0
|
||||
refsLoaded = hasRefs
|
||||
const space = isSpace(object) ? object._id : object.space
|
||||
dataProvider = new ChannelDataProvider(ctx, space, attachedTo, _class, selectedMessageId, loadAll)
|
||||
dataProvider = new ChannelDataProvider(ctx, space, attachedTo, _class, selectedMessageId, loadAll, hasRefs)
|
||||
}
|
||||
}
|
||||
|
||||
$: if (dataProvider && !refsLoaded && ((object as WithReferences<Doc>).references ?? 0) > 0) {
|
||||
dataProvider.loadRefs()
|
||||
refsLoaded = true
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if dataProvider}
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
import { getDocTitle } from '@hcengineering/view-resources'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import { Channel } from '@hcengineering/chunter'
|
||||
import { ActivityMessagesFilter } from '@hcengineering/activity'
|
||||
import { ActivityMessagesFilter, WithReferences } from '@hcengineering/activity'
|
||||
import contact from '@hcengineering/contact'
|
||||
|
||||
import Header from './Header.svelte'
|
||||
@@ -27,7 +27,7 @@
|
||||
|
||||
export let _id: Ref<Doc>
|
||||
export let _class: Ref<Class<Doc>>
|
||||
export let object: Doc | undefined
|
||||
export let object: WithReferences<Doc> | undefined
|
||||
export let allowClose: boolean = false
|
||||
export let canOpen: boolean = false
|
||||
export let withAside: boolean = false
|
||||
@@ -78,6 +78,6 @@
|
||||
on:close
|
||||
>
|
||||
{#if object}
|
||||
<PinnedMessages {_id} {_class} space={object.space} on:select />
|
||||
<PinnedMessages {_id} {_class} space={object.space} withRefs={(object.references ?? 0) > 0} on:select />
|
||||
{/if}
|
||||
</Header>
|
||||
|
||||
@@ -646,10 +646,7 @@
|
||||
return
|
||||
}
|
||||
|
||||
const lastMetadata = metadata[metadata.length - 1]
|
||||
const lastMessage = displayMessages[displayMessages.length - 1]
|
||||
|
||||
if (lastMetadata._id !== lastMessage._id) {
|
||||
if (!$isTailLoadedStore) {
|
||||
showScrollDownButton = true
|
||||
} else if (element != null) {
|
||||
const { scrollHeight, scrollTop, offsetHeight } = element
|
||||
|
||||
@@ -17,8 +17,8 @@
|
||||
import attachment, { Attachment } from '@hcengineering/attachment'
|
||||
import { AttachmentPresenter, FileDownload } from '@hcengineering/attachment-resources'
|
||||
import { ChunterSpace } from '@hcengineering/chunter'
|
||||
import core, { Doc, SortingOrder, getCurrentAccount, type WithLookup } from '@hcengineering/core'
|
||||
import { createQuery, getBlobHref, getClient } from '@hcengineering/presentation'
|
||||
import { Doc, SortingOrder, getCurrentAccount, type WithLookup } from '@hcengineering/core'
|
||||
import { createQuery, getClient, getFileUrl } from '@hcengineering/presentation'
|
||||
import { Icon, IconMoreV, Label, Menu, getCurrentResolvedLocation, navigate, showPopup } from '@hcengineering/ui'
|
||||
|
||||
export let channel: ChunterSpace | undefined
|
||||
@@ -68,10 +68,7 @@
|
||||
{
|
||||
limit: ATTACHEMNTS_LIMIT,
|
||||
sort,
|
||||
total: true,
|
||||
lookup: {
|
||||
file: core.class.Blob
|
||||
}
|
||||
total: true
|
||||
}
|
||||
)
|
||||
</script>
|
||||
@@ -86,11 +83,9 @@
|
||||
<AttachmentPresenter value={attachment} />
|
||||
</div>
|
||||
<div class="eAttachmentRowActions" class:fixed={i === selectedRowNumber}>
|
||||
{#await getBlobHref(attachment.$lookup?.file, attachment.file, attachment.name) then blobRef}
|
||||
<a href={blobRef} download={attachment.name}>
|
||||
<Icon icon={FileDownload} size={'small'} />
|
||||
</a>
|
||||
{/await}
|
||||
<a href={getFileUrl(attachment.file, attachment.name)} download={attachment.name}>
|
||||
<Icon icon={FileDownload} size={'small'} />
|
||||
</a>
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<div id="context-menu" class="eAttachmentRowMenu" on:click={(event) => showMenu(event, attachment, i)}>
|
||||
|
||||
@@ -28,13 +28,16 @@
|
||||
export let space: Ref<Space>
|
||||
export let _class: Ref<Class<Doc>>
|
||||
export let _id: Ref<Doc>
|
||||
export let withRefs = false
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
const pinnedQuery = createQuery()
|
||||
const pinnedThreadsQuery = createQuery()
|
||||
const pinnedRefsQuery = createQuery()
|
||||
|
||||
let pinnedMessagesCount = 0
|
||||
let pinnedThreadsCount = 0
|
||||
let refsCount = 0
|
||||
|
||||
$: channelSpace = getChannelSpace(_class, _id, space)
|
||||
$: pinnedQuery.query(
|
||||
@@ -55,10 +58,21 @@
|
||||
{ projection: { _id: 1, space: 1, objectId: 1, isPinned: 1 } }
|
||||
)
|
||||
|
||||
$: if (withRefs) {
|
||||
pinnedRefsQuery.query(
|
||||
activity.class.ActivityReference,
|
||||
{ attachedTo: _id, isPinned: true, space: { $ne: channelSpace } },
|
||||
(res) => {
|
||||
refsCount = res.total
|
||||
},
|
||||
{ limit: 1, total: true }
|
||||
)
|
||||
}
|
||||
|
||||
function openMessagesPopup (ev: MouseEvent): void {
|
||||
showPopup(
|
||||
PinnedMessagesPopup,
|
||||
{ attachedTo: _id, attachedToClass: _class, space: channelSpace },
|
||||
{ attachedTo: _id, attachedToClass: _class, space: channelSpace, withRefs },
|
||||
eventToHTMLElement(ev),
|
||||
(result) => {
|
||||
if (result == null) return
|
||||
@@ -67,7 +81,7 @@
|
||||
)
|
||||
}
|
||||
|
||||
$: count = pinnedMessagesCount + pinnedThreadsCount
|
||||
$: count = pinnedMessagesCount + pinnedThreadsCount + refsCount
|
||||
</script>
|
||||
|
||||
{#if count > 0}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||
import activity, { ActivityMessage } from '@hcengineering/activity'
|
||||
import activity, { ActivityMessage, ActivityReference } from '@hcengineering/activity'
|
||||
import { ActivityMessagePresenter, sortActivityMessages } from '@hcengineering/activity-resources'
|
||||
import { ActionIcon, IconClose } from '@hcengineering/ui'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
@@ -26,14 +26,17 @@
|
||||
export let attachedTo: Ref<Doc>
|
||||
export let attachedToClass: Ref<Class<Doc>>
|
||||
export let space: Ref<Space>
|
||||
export let withRefs = false
|
||||
|
||||
const client = getClient()
|
||||
const dispatch = createEventDispatcher()
|
||||
const pinnedQuery = createQuery()
|
||||
const pinnedThreadsQuery = createQuery()
|
||||
const pinnedRefsQuery = createQuery()
|
||||
|
||||
let pinnedMessages: ActivityMessage[] = []
|
||||
let pinnedThreads: ThreadMessage[] = []
|
||||
let pinnedRefs: ActivityReference[] = []
|
||||
|
||||
$: pinnedQuery.query(
|
||||
activity.class.ActivityMessage,
|
||||
@@ -51,6 +54,16 @@
|
||||
}
|
||||
)
|
||||
|
||||
$: if (withRefs) {
|
||||
pinnedRefsQuery.query(
|
||||
activity.class.ActivityReference,
|
||||
{ attachedTo, isPinned: true, space: { $ne: space } },
|
||||
(res) => {
|
||||
pinnedRefs = res
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
$: if (pinnedMessages.length === 0 && pinnedThreads.length === 0) {
|
||||
dispatch('close', undefined)
|
||||
}
|
||||
@@ -59,7 +72,10 @@
|
||||
await client.update(message, { isPinned: false })
|
||||
}
|
||||
|
||||
$: displayMessages = sortActivityMessages(pinnedMessages.concat(pinnedThreads), SortingOrder.Descending)
|
||||
$: displayMessages = sortActivityMessages(
|
||||
pinnedMessages.concat(pinnedThreads).concat(pinnedRefs),
|
||||
SortingOrder.Descending
|
||||
)
|
||||
</script>
|
||||
|
||||
<div class="antiPopup vScroll popup">
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<script lang="ts">
|
||||
import { Person, PersonAccount } from '@hcengineering/contact'
|
||||
import { personAccountByIdStore, personByIdStore } from '@hcengineering/contact-resources'
|
||||
import { Class, Doc, getCurrentAccount, Ref, WithLookup } from '@hcengineering/core'
|
||||
import { Class, Doc, getCurrentAccount, Ref, Space, WithLookup } from '@hcengineering/core'
|
||||
import { getClient, MessageViewer } from '@hcengineering/presentation'
|
||||
import { AttachmentDocList, AttachmentImageSize } from '@hcengineering/attachment-resources'
|
||||
import { getDocLinkTitle } from '@hcengineering/view-resources'
|
||||
@@ -80,7 +80,7 @@
|
||||
$: person = account?.person !== undefined ? $personByIdStore.get(account.person) : undefined
|
||||
|
||||
$: value !== undefined &&
|
||||
getParentMessage(value.attachedToClass, value.attachedTo).then((res) => {
|
||||
getParentMessage(value.attachedToClass, value.attachedTo, value.space).then((res) => {
|
||||
parentMessage = res as DisplayActivityMessage
|
||||
})
|
||||
|
||||
@@ -107,9 +107,13 @@
|
||||
stale = false
|
||||
}
|
||||
|
||||
async function getParentMessage (_class: Ref<Class<Doc>>, _id: Ref<Doc>): Promise<ActivityMessage | undefined> {
|
||||
async function getParentMessage (
|
||||
_class: Ref<Class<Doc>>,
|
||||
_id: Ref<Doc>,
|
||||
space: Ref<Space>
|
||||
): Promise<ActivityMessage | undefined> {
|
||||
if (hierarchy.isDerived(_class, activity.class.ActivityMessage)) {
|
||||
return await client.findOne<ActivityMessage>(_class, { _id: _id as Ref<ActivityMessage> })
|
||||
return await client.findOne<ActivityMessage>(_class, { _id: _id as Ref<ActivityMessage>, space })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -41,11 +41,6 @@
|
||||
},
|
||||
(res) => {
|
||||
attachments = res
|
||||
},
|
||||
{
|
||||
lookup: {
|
||||
file: core.class.Blob
|
||||
}
|
||||
}
|
||||
)
|
||||
} else {
|
||||
|
||||
+44
-56
@@ -27,79 +27,67 @@
|
||||
import ThreadMessagePreview from '../threads/ThreadMessagePreview.svelte'
|
||||
|
||||
export let context: DocNotifyContext
|
||||
export let object: ChatMessage
|
||||
|
||||
const client = getClient()
|
||||
const hierarchy = client.getHierarchy()
|
||||
|
||||
let title: string | undefined = undefined
|
||||
let parentMessage: ChatMessage | undefined = undefined
|
||||
let object: Doc | undefined = undefined
|
||||
let channel: Doc | undefined = undefined
|
||||
|
||||
$: isThread = hierarchy.isDerived(context.attachedToClass, chunter.class.ThreadMessage)
|
||||
|
||||
$: void client
|
||||
.findOne(context.attachedToClass as Ref<Class<ChatMessage>>, { _id: context.attachedTo as Ref<ChatMessage> })
|
||||
.then((res) => {
|
||||
parentMessage = res
|
||||
})
|
||||
|
||||
$: loadObject(parentMessage, isThread)
|
||||
$: object &&
|
||||
getDocLinkTitle(client, object._id, object._class, object).then((res) => {
|
||||
$: loadChannel(object, isThread)
|
||||
$: channel &&
|
||||
getDocLinkTitle(client, channel._id, channel._class, channel).then((res) => {
|
||||
title = res
|
||||
})
|
||||
|
||||
function loadObject (parentMessage: ChatMessage | undefined, isThread: boolean): void {
|
||||
if (parentMessage === undefined) {
|
||||
object = undefined
|
||||
return
|
||||
}
|
||||
|
||||
const _class = isThread ? (parentMessage as ThreadMessage).objectClass : parentMessage.attachedToClass
|
||||
const _id = isThread ? (parentMessage as ThreadMessage).objectId : parentMessage.attachedTo
|
||||
|
||||
void client.findOne(_class, { _id }).then((res) => {
|
||||
object = res
|
||||
function loadChannel (object: ChatMessage, isThread: boolean): void {
|
||||
const _class = isThread ? (object as ThreadMessage).objectClass : object.attachedToClass
|
||||
const _id = isThread ? (object as ThreadMessage).objectId : object.attachedTo
|
||||
console.log({ _class, _id, isThread, object })
|
||||
void client.findOne(_class, { _id, ...(isThread ? { space: object.space } : {}) }).then((res) => {
|
||||
channel = res
|
||||
})
|
||||
}
|
||||
|
||||
function toThread (message: ChatMessage): ThreadMessage {
|
||||
return message as ThreadMessage
|
||||
}
|
||||
|
||||
function isAvatarIcon (_class: Ref<Class<Doc>>): boolean {
|
||||
return hierarchy.isDerived(_class, contact.class.Person) || hierarchy.isDerived(_class, chunter.class.DirectMessage)
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if parentMessage}
|
||||
<span class="flex-presenter flex-gap-1 font-semi-bold">
|
||||
{#if isThread || (parentMessage.replies ?? 0) > 0}
|
||||
<Label label={chunter.string.Thread} />
|
||||
{:else}
|
||||
<Label label={chunter.string.Message} />
|
||||
{/if}
|
||||
{#if title && object}
|
||||
<span class="lower">
|
||||
<Label label={chunter.string.In} />
|
||||
</span>
|
||||
{#await getDocTitle(client, object._id, object._class, object) then tooltipLabel}
|
||||
<span
|
||||
class="flex-presenter flex-gap-0-5"
|
||||
use:tooltip={tooltipLabel ? { label: getEmbeddedLabel(tooltipLabel) } : undefined}
|
||||
>
|
||||
<ObjectIcon
|
||||
value={object}
|
||||
size={hierarchy.isDerived(object._class, contact.class.Person) ? 'tiny' : 'small'}
|
||||
/>
|
||||
<span class="overflow-label">
|
||||
{title}
|
||||
</span>
|
||||
<span class="flex-presenter flex-gap-1 font-semi-bold">
|
||||
{#if isThread || (object.replies ?? 0) > 0}
|
||||
<Label label={chunter.string.Thread} />
|
||||
{:else}
|
||||
<Label label={chunter.string.Message} />
|
||||
{/if}
|
||||
{#if title && channel}
|
||||
<span class="lower">
|
||||
<Label label={chunter.string.In} />
|
||||
</span>
|
||||
{#await getDocTitle(client, channel._id, channel._class, channel) then tooltipLabel}
|
||||
<span
|
||||
class="flex-presenter flex-gap-0-5"
|
||||
use:tooltip={tooltipLabel ? { label: getEmbeddedLabel(tooltipLabel) } : undefined}
|
||||
>
|
||||
<ObjectIcon value={channel} size={isAvatarIcon(channel._class) ? 'tiny' : 'small'} />
|
||||
<span class="overflow-label">
|
||||
{title}
|
||||
</span>
|
||||
{/await}
|
||||
{/if}
|
||||
</span>
|
||||
<span class="font-normal">
|
||||
{#if isThread}
|
||||
<ThreadMessagePreview value={toThread(parentMessage)} readonly type="content-only" />
|
||||
{:else}
|
||||
<ChatMessagePreview value={parentMessage} readonly type="content-only" />
|
||||
{/if}
|
||||
</span>
|
||||
{/if}
|
||||
</span>
|
||||
{/await}
|
||||
{/if}
|
||||
</span>
|
||||
<span class="font-normal">
|
||||
{#if isThread}
|
||||
<ThreadMessagePreview value={toThread(object)} readonly type="content-only" />
|
||||
{:else}
|
||||
<ChatMessagePreview value={object} readonly type="content-only" />
|
||||
{/if}
|
||||
</span>
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
|
||||
$: if (empValue === undefined) {
|
||||
void getClient()
|
||||
.findOne(contact.class.Contact, { _id }, { lookup: { avatar: core.class.Blob } })
|
||||
.findOne(contact.class.Contact, { _id })
|
||||
.then((c) => {
|
||||
_contact = c
|
||||
})
|
||||
|
||||
@@ -15,11 +15,11 @@
|
||||
//
|
||||
|
||||
import {
|
||||
type Channel,
|
||||
type AvatarInfo,
|
||||
type Contact,
|
||||
getGravatarUrl,
|
||||
getName,
|
||||
type AvatarInfo,
|
||||
type Channel,
|
||||
type Contact,
|
||||
type Person,
|
||||
type PersonAccount
|
||||
} from '@hcengineering/contact'
|
||||
@@ -49,6 +49,7 @@ import {
|
||||
type ColorDefinition,
|
||||
type TooltipAlignment
|
||||
} from '@hcengineering/ui'
|
||||
import { AggregationManager } from '@hcengineering/view-resources'
|
||||
import AccountArrayEditor from './components/AccountArrayEditor.svelte'
|
||||
import AccountBox from './components/AccountBox.svelte'
|
||||
import AssigneeBox from './components/AssigneeBox.svelte'
|
||||
@@ -122,7 +123,6 @@ import NameChangedActivityMessage from './components/activity/NameChangedActivit
|
||||
import IconAddMember from './components/icons/AddMember.svelte'
|
||||
import ExpandRightDouble from './components/icons/ExpandRightDouble.svelte'
|
||||
import IconMembers from './components/icons/Members.svelte'
|
||||
import { AggregationManager } from '@hcengineering/view-resources'
|
||||
|
||||
import { get, writable } from 'svelte/store'
|
||||
import contact from './plugin'
|
||||
@@ -408,7 +408,7 @@ export default async (): Promise<Resources> => ({
|
||||
color: getPersonColor(person, name)
|
||||
}
|
||||
}
|
||||
const blobRef = await getBlobRef(person.$lookup?.avatar, person.avatar, undefined, width)
|
||||
const blobRef = await getBlobRef(person.avatar, undefined, width)
|
||||
return {
|
||||
url: blobRef.src,
|
||||
srcSet: blobRef.srcset,
|
||||
|
||||
@@ -320,19 +320,10 @@ function fillStores (): void {
|
||||
const accountPersonQuery = createQuery(true)
|
||||
|
||||
const query = createQuery(true)
|
||||
query.query(
|
||||
contact.mixin.Employee,
|
||||
{},
|
||||
(res) => {
|
||||
employeesStore.set(res)
|
||||
employeeByIdStore.set(toIdMap(res))
|
||||
},
|
||||
{
|
||||
lookup: {
|
||||
avatar: core.class.Blob
|
||||
}
|
||||
}
|
||||
)
|
||||
query.query(contact.mixin.Employee, {}, (res) => {
|
||||
employeesStore.set(res)
|
||||
employeeByIdStore.set(toIdMap(res))
|
||||
})
|
||||
|
||||
const accountQ = createQuery(true)
|
||||
accountQ.query(contact.class.PersonAccount, {}, (res) => {
|
||||
@@ -345,11 +336,6 @@ function fillStores (): void {
|
||||
{ _id: { $in: persons }, [contact.mixin.Employee]: { $exists: false } },
|
||||
(res) => {
|
||||
personAccountPersonByIdStore.set(toIdMap(res))
|
||||
},
|
||||
{
|
||||
lookup: {
|
||||
avatar: core.class.Blob
|
||||
}
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import core, { type Blob, type WithLookup } from '@hcengineering/core'
|
||||
import { type Blob, type Ref, type WithLookup } from '@hcengineering/core'
|
||||
import drive, { type File, type FileVersion } from '@hcengineering/drive'
|
||||
import { FilePreview, createQuery } from '@hcengineering/presentation'
|
||||
|
||||
@@ -26,32 +26,23 @@
|
||||
const dispatch = createEventDispatcher()
|
||||
const query = createQuery()
|
||||
|
||||
let blob: Blob | undefined = undefined
|
||||
let blob: Ref<Blob> | undefined = undefined
|
||||
let version: WithLookup<FileVersion> | undefined = undefined
|
||||
let contentType: string | undefined
|
||||
|
||||
$: query.query(
|
||||
drive.class.FileVersion,
|
||||
{ _id: object.file },
|
||||
(res) => {
|
||||
;[version] = res
|
||||
blob = version?.$lookup?.file
|
||||
},
|
||||
{
|
||||
lookup: {
|
||||
file: core.class.Blob
|
||||
}
|
||||
}
|
||||
)
|
||||
$: query.query(drive.class.FileVersion, { _id: object.file }, (res) => {
|
||||
;[version] = res
|
||||
blob = version.file
|
||||
contentType = version.type
|
||||
})
|
||||
|
||||
onMount(() => {
|
||||
dispatch('open', { ignoreKeys: ['parent', 'path', 'version', 'versions'] })
|
||||
})
|
||||
</script>
|
||||
|
||||
{#if object !== undefined && version !== undefined}
|
||||
{#if blob !== undefined}
|
||||
<FilePreview file={blob} name={version.name} metadata={version.metadata} fit />
|
||||
{/if}
|
||||
{#if object !== undefined && version !== undefined && blob !== undefined && contentType !== undefined}
|
||||
<FilePreview file={blob} {contentType} name={version.name} metadata={version.metadata} fit />
|
||||
|
||||
{#if object.versions > 1}
|
||||
<div class="w-full mt-6">
|
||||
|
||||
@@ -24,7 +24,6 @@
|
||||
export let readonly: boolean = false
|
||||
|
||||
const options: FindOptions<FileVersion> = {
|
||||
lookup: { file: core.class.Blob },
|
||||
sort: { version: SortingOrder.Descending }
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -24,9 +24,5 @@
|
||||
|
||||
<Scroller>
|
||||
<DocAttributeBar {object} {readonly} ignoreKeys={[]} />
|
||||
|
||||
{#if object.$lookup?.file}
|
||||
<DocAttributeBar object={object.$lookup.file} {readonly} ignoreKeys={['name', 'file', 'version', 'version']} />
|
||||
{/if}
|
||||
<div class="space-divider bottom" />
|
||||
</Scroller>
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
import { type Ref, type WithLookup } from '@hcengineering/core'
|
||||
import { createFileVersion, type File as DriveFile, type FileVersion } from '@hcengineering/drive'
|
||||
import { Panel } from '@hcengineering/panel'
|
||||
import { createQuery, getBlobHref, getClient } from '@hcengineering/presentation'
|
||||
import { createQuery, getClient, getFileUrl } from '@hcengineering/presentation'
|
||||
import { Button, IconMoreH } from '@hcengineering/ui'
|
||||
import { showFilesUploadPopup } from '@hcengineering/uploader'
|
||||
import view from '@hcengineering/view'
|
||||
@@ -108,17 +108,15 @@
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="utils">
|
||||
{#await getBlobHref(undefined, version.file, object.name) then href}
|
||||
<a class="no-line" {href} download={object.name} bind:this={download}>
|
||||
<Button
|
||||
icon={IconDownload}
|
||||
iconProps={{ size: 'medium' }}
|
||||
kind={'icon'}
|
||||
showTooltip={{ label: drive.string.Download }}
|
||||
on:click={handleDownloadFile}
|
||||
/>
|
||||
</a>
|
||||
{/await}
|
||||
<a class="no-line" href={getFileUrl(version.file, object.name)} download={object.name} bind:this={download}>
|
||||
<Button
|
||||
icon={IconDownload}
|
||||
iconProps={{ size: 'medium' }}
|
||||
kind={'icon'}
|
||||
showTooltip={{ label: drive.string.Download }}
|
||||
on:click={handleDownloadFile}
|
||||
/>
|
||||
</a>
|
||||
<Button
|
||||
icon={IconUpload}
|
||||
iconProps={{ size: 'medium' }}
|
||||
|
||||
@@ -38,17 +38,11 @@
|
||||
return
|
||||
}
|
||||
|
||||
if (value.$lookup?.file === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
const blob = value.$lookup?.file
|
||||
|
||||
showPopup(
|
||||
FilePreviewPopup,
|
||||
{
|
||||
file: blob._id,
|
||||
contentType: blob.contentType,
|
||||
file: value.file,
|
||||
contentType: value.type,
|
||||
name: value.name,
|
||||
metadata: value.metadata
|
||||
},
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
{#if isFolder}
|
||||
<Icon icon={IconFolderThumbnail} size={'full'} fill={'var(--global-no-priority-PriorityColor)'} />
|
||||
{:else if previewRef != null && isImage && !isError}
|
||||
{#await getBlobRef(undefined, previewRef, object.name, sizeToWidth(size)) then blobSrc}
|
||||
{#await getBlobRef(previewRef, object.name, sizeToWidth(size)) then blobSrc}
|
||||
<img
|
||||
draggable="false"
|
||||
class="img-fit"
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
import { type Doc, type Ref, type WithLookup } from '@hcengineering/core'
|
||||
import drive, { type Drive, type File, type FileVersion, type Folder } from '@hcengineering/drive'
|
||||
import { type Resources } from '@hcengineering/platform'
|
||||
import { getBlobHref } from '@hcengineering/presentation'
|
||||
import { showPopup, type Location } from '@hcengineering/ui'
|
||||
|
||||
import CreateDrive from './components/CreateDrive.svelte'
|
||||
@@ -37,8 +36,9 @@ import GridView from './components/GridView.svelte'
|
||||
import MoveResource from './components/MoveResource.svelte'
|
||||
import ResourcePresenter from './components/ResourcePresenter.svelte'
|
||||
|
||||
import { getFileUrl } from '@hcengineering/presentation'
|
||||
import { getDriveLink, getFileLink, getFolderLink, resolveLocation } from './navigation'
|
||||
import { showCreateFolderPopup, showRenameResourcePopup, restoreFileVersion } from './utils'
|
||||
import { restoreFileVersion, showCreateFolderPopup, showRenameResourcePopup } from './utils'
|
||||
|
||||
async function CreateRootFolder (doc: Drive): Promise<void> {
|
||||
await showCreateFolderPopup(doc._id, drive.ids.Root)
|
||||
@@ -57,7 +57,7 @@ async function DownloadFile (doc: WithLookup<File> | Array<WithLookup<File>>): P
|
||||
for (const file of files) {
|
||||
const version = file.$lookup?.file
|
||||
if (version != null) {
|
||||
const href = await getBlobHref(undefined, version.file, version.name)
|
||||
const href = getFileUrl(version.file, version.name)
|
||||
const link = document.createElement('a')
|
||||
link.style.display = 'none'
|
||||
link.target = '_blank'
|
||||
|
||||
@@ -52,11 +52,6 @@
|
||||
},
|
||||
(res) => {
|
||||
attachments = res
|
||||
},
|
||||
{
|
||||
lookup: {
|
||||
file: core.class.Blob
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -36,11 +36,6 @@
|
||||
},
|
||||
(res) => {
|
||||
attachments = res
|
||||
},
|
||||
{
|
||||
lookup: {
|
||||
file: core.class.Blob
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -20,10 +20,10 @@
|
||||
DocNotifyContext,
|
||||
InboxNotification
|
||||
} from '@hcengineering/notification'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import { createQuery, getClient, isSpace, isSpaceClass } from '@hcengineering/presentation'
|
||||
import { getDocTitle, getDocIdentifier, Menu } from '@hcengineering/view-resources'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { Class, Doc, IdMap, Ref, WithLookup } from '@hcengineering/core'
|
||||
import core, { Class, Doc, IdMap, Ref, WithLookup } from '@hcengineering/core'
|
||||
import chunter from '@hcengineering/chunter'
|
||||
import { personAccountByIdStore } from '@hcengineering/contact-resources'
|
||||
import { Person, PersonAccount } from '@hcengineering/contact'
|
||||
@@ -47,6 +47,22 @@
|
||||
const client = getClient()
|
||||
const hierarchy = client.getHierarchy()
|
||||
const dispatch = createEventDispatcher()
|
||||
const query = createQuery()
|
||||
|
||||
let object: Doc | undefined = undefined
|
||||
|
||||
$: query.query(
|
||||
value.attachedToClass,
|
||||
{ _id: value.attachedTo, space: isSpaceClass(value.attachedToClass) ? core.space.Space : value.space },
|
||||
(res) => {
|
||||
object = res[0]
|
||||
},
|
||||
{ limit: 1 }
|
||||
)
|
||||
|
||||
$: if (object?._id !== value.attachedTo) {
|
||||
object = undefined
|
||||
}
|
||||
|
||||
let isActionMenuOpened = false
|
||||
let unreadCount = 0
|
||||
@@ -56,13 +72,15 @@
|
||||
let idTitle: string | undefined
|
||||
let title: string | undefined
|
||||
|
||||
$: void getDocIdentifier(client, value.attachedTo, value.attachedToClass).then((res) => {
|
||||
idTitle = res
|
||||
})
|
||||
$: object &&
|
||||
getDocIdentifier(client, object._id, object._class, object).then((res) => {
|
||||
idTitle = res
|
||||
})
|
||||
|
||||
$: void getDocTitle(client, value.attachedTo, value.attachedToClass).then((res) => {
|
||||
title = res
|
||||
})
|
||||
$: object &&
|
||||
getDocTitle(client, object._id, object._class, object).then((res) => {
|
||||
title = res
|
||||
})
|
||||
|
||||
$: presenterMixin = hierarchy.classHierarchyMixin(
|
||||
value.attachedToClass,
|
||||
@@ -184,73 +202,75 @@
|
||||
dispatch('click', { context: value })
|
||||
}}
|
||||
>
|
||||
<div class="header">
|
||||
<NotifyContextIcon {value} notifyCount={unreadCount} />
|
||||
{#if object}
|
||||
<div class="header">
|
||||
<NotifyContextIcon {value} notifyCount={unreadCount} {object} />
|
||||
|
||||
<div class="labels">
|
||||
{#if presenterMixin?.labelPresenter}
|
||||
<Component is={presenterMixin.labelPresenter} props={{ context: value }} />
|
||||
{:else}
|
||||
{#if idTitle}
|
||||
{idTitle}
|
||||
<div class="labels">
|
||||
{#if presenterMixin?.labelPresenter}
|
||||
<Component is={presenterMixin.labelPresenter} props={{ context: value, object }} />
|
||||
{:else}
|
||||
<Label label={hierarchy.getClass(value.attachedToClass).label} />
|
||||
{/if}
|
||||
<span class="title overflow-label clear-mins" {title}>
|
||||
{#if title}
|
||||
{title}
|
||||
{#if idTitle}
|
||||
{idTitle}
|
||||
{:else}
|
||||
<Label label={hierarchy.getClass(value.attachedToClass).label} />
|
||||
{/if}
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="actions clear-mins">
|
||||
<div class="flex-center">
|
||||
{#if archivingPromise !== undefined}
|
||||
<Spinner size="small" />
|
||||
{:else}
|
||||
<CheckBox checked={archived} kind="todo" size="medium" on:value={checkContext} />
|
||||
<span class="title overflow-label clear-mins" {title}>
|
||||
{#if title}
|
||||
{title}
|
||||
{:else}
|
||||
<Label label={hierarchy.getClass(value.attachedToClass).label} />
|
||||
{/if}
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
<ButtonIcon
|
||||
icon={IconMoreV}
|
||||
size="small"
|
||||
kind="tertiary"
|
||||
inheritColor
|
||||
pressed={isActionMenuOpened}
|
||||
on:click={showMenu}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<div class="notifications">
|
||||
{#each groupedNotifications.slice(0, maxNotifications) as group (getKey(group))}
|
||||
<div class="notification">
|
||||
<!-- use:tooltip={canShowTooltip(group)-->
|
||||
<!-- ? {-->
|
||||
<!-- component: MessagesPopup,-->
|
||||
<!-- props: { context: value, notifications: group }-->
|
||||
<!-- }-->
|
||||
<!-- : undefined}-->
|
||||
|
||||
<div class="embeddedMarker" />
|
||||
<InboxNotificationPresenter
|
||||
value={group[0]}
|
||||
{viewlets}
|
||||
space={value.space}
|
||||
on:click={(e) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
dispatch('click', { context: value, notification: group[0] })
|
||||
}}
|
||||
/>
|
||||
<div class="actions clear-mins">
|
||||
<div class="flex-center">
|
||||
{#if archivingPromise !== undefined}
|
||||
<Spinner size="small" />
|
||||
{:else}
|
||||
<CheckBox checked={archived} kind="todo" size="medium" on:value={checkContext} />
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
<ButtonIcon
|
||||
icon={IconMoreV}
|
||||
size="small"
|
||||
kind="tertiary"
|
||||
inheritColor
|
||||
pressed={isActionMenuOpened}
|
||||
on:click={showMenu}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content">
|
||||
<div class="notifications">
|
||||
{#each groupedNotifications.slice(0, maxNotifications) as group (getKey(group))}
|
||||
<div class="notification">
|
||||
<!-- use:tooltip={canShowTooltip(group)-->
|
||||
<!-- ? {-->
|
||||
<!-- component: MessagesPopup,-->
|
||||
<!-- props: { context: value, notifications: group }-->
|
||||
<!-- }-->
|
||||
<!-- : undefined}-->
|
||||
|
||||
<div class="embeddedMarker" />
|
||||
<InboxNotificationPresenter
|
||||
value={group[0]}
|
||||
{object}
|
||||
{viewlets}
|
||||
space={value.space}
|
||||
on:click={(e) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
dispatch('click', { context: value, notification: group[0] })
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
@@ -261,6 +281,7 @@
|
||||
cursor: pointer;
|
||||
padding: var(--spacing-1_5) var(--spacing-1);
|
||||
border-bottom: 1px solid var(--global-ui-BorderColor);
|
||||
min-height: 5.625rem;
|
||||
|
||||
.header {
|
||||
position: relative;
|
||||
|
||||
@@ -14,9 +14,9 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { DocNotifyContext } from '@hcengineering/notification'
|
||||
import { Doc } from '@hcengineering/core'
|
||||
import core, { Doc } from '@hcengineering/core'
|
||||
import { getDocLinkTitle, getDocTitle } from '@hcengineering/view-resources'
|
||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||
import { createQuery, getClient, isSpaceClass } from '@hcengineering/presentation'
|
||||
import chunter from '@hcengineering/chunter'
|
||||
import NotifyContextIcon from './NotifyContextIcon.svelte'
|
||||
|
||||
@@ -27,9 +27,13 @@
|
||||
|
||||
let object: Doc | undefined
|
||||
|
||||
$: objectQuery.query(value.attachedToClass, { _id: value.attachedTo }, (res) => {
|
||||
object = res[0]
|
||||
})
|
||||
$: objectQuery.query(
|
||||
value.attachedToClass,
|
||||
{ _id: value.attachedTo, space: isSpaceClass(value.attachedToClass) ? core.space.Space : value.space },
|
||||
(res) => {
|
||||
object = res[0]
|
||||
}
|
||||
)
|
||||
|
||||
async function getTitle (object: Doc) {
|
||||
if (object._class === chunter.class.DirectMessage) {
|
||||
@@ -41,7 +45,7 @@
|
||||
|
||||
{#if object}
|
||||
<div class="flex-presenter">
|
||||
<NotifyContextIcon {value} size="small" />
|
||||
<NotifyContextIcon {value} {object} size="small" />
|
||||
<div class="mr-4" />
|
||||
|
||||
{#await getTitle(object) then title}
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<script lang="ts">
|
||||
import notification, { DocNotifyContext } from '@hcengineering/notification'
|
||||
import { Component, Icon, IconSize } from '@hcengineering/ui'
|
||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import { classIcon } from '@hcengineering/view-resources'
|
||||
import view from '@hcengineering/view'
|
||||
import { Doc } from '@hcengineering/core'
|
||||
@@ -25,21 +25,12 @@
|
||||
export let value: DocNotifyContext
|
||||
export let size: IconSize = 'medium'
|
||||
export let notifyCount: number = 0
|
||||
export let object: Doc | undefined
|
||||
|
||||
const client = getClient()
|
||||
const hierarchy = client.getHierarchy()
|
||||
const query = createQuery()
|
||||
|
||||
let object: Doc | undefined = undefined
|
||||
|
||||
$: if (object?._id !== value.attachedTo) {
|
||||
object = undefined
|
||||
}
|
||||
$: iconMixin = hierarchy.classHierarchyMixin(value.attachedToClass, view.mixin.ObjectIcon)
|
||||
$: iconMixin &&
|
||||
query.query(value.attachedToClass, { _id: value.attachedTo }, (res) => {
|
||||
object = res[0]
|
||||
})
|
||||
</script>
|
||||
|
||||
<div class="container">
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
|
||||
$: void client
|
||||
.findAll(activity.class.Reaction, {
|
||||
space: message.space,
|
||||
_id: { $in: [message.objectId, ...(message?.previousMessages?.map((a) => a.objectId) ?? [])] as Ref<Reaction>[] }
|
||||
})
|
||||
.then((res) => {
|
||||
|
||||
+3
-2
@@ -14,7 +14,7 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import { Ref, Space, matchQuery } from '@hcengineering/core'
|
||||
import { Ref, Space, matchQuery, Doc } from '@hcengineering/core'
|
||||
import notification, {
|
||||
ActivityInboxNotification,
|
||||
ActivityNotificationViewlet,
|
||||
@@ -30,6 +30,7 @@
|
||||
import { getActions } from '@hcengineering/view-resources'
|
||||
import { getResource } from '@hcengineering/platform'
|
||||
|
||||
export let object: Doc | undefined
|
||||
export let value: DisplayActivityInboxNotification
|
||||
export let viewlets: ActivityNotificationViewlet[] = []
|
||||
export let space: Ref<Space> | undefined = undefined
|
||||
@@ -100,6 +101,6 @@
|
||||
on:click
|
||||
/>
|
||||
{:else}
|
||||
<ActivityMessagePreview value={displayMessage} {actions} {space} on:click />
|
||||
<ActivityMessagePreview value={displayMessage} {actions} {space} doc={object} on:click />
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
+2
-1
@@ -20,6 +20,7 @@
|
||||
import { ActivityNotificationViewlet, DisplayInboxNotification } from '@hcengineering/notification'
|
||||
|
||||
export let value: DisplayInboxNotification
|
||||
export let object: Doc | undefined
|
||||
export let viewlets: ActivityNotificationViewlet[] = []
|
||||
export let space: Ref<Space> | undefined = undefined
|
||||
|
||||
@@ -30,5 +31,5 @@
|
||||
</script>
|
||||
|
||||
{#if objectPresenter}
|
||||
<Component is={objectPresenter.presenter} props={{ value, viewlets, space }} on:click />
|
||||
<Component is={objectPresenter.presenter} props={{ value, viewlets, space, object }} on:click />
|
||||
{/if}
|
||||
|
||||
@@ -76,9 +76,13 @@ const providerSettingsQuery = createQuery(true)
|
||||
const typeSettingsQuery = createQuery(true)
|
||||
|
||||
export function loadNotificationSettings (): void {
|
||||
providerSettingsQuery.query(notification.class.NotificationProviderSetting, {}, (res) => {
|
||||
providersSettings.set(res)
|
||||
})
|
||||
providerSettingsQuery.query(
|
||||
notification.class.NotificationProviderSetting,
|
||||
{ space: core.space.Workspace },
|
||||
(res) => {
|
||||
providersSettings.set(res)
|
||||
}
|
||||
)
|
||||
typeSettingsQuery.query(notification.class.NotificationTypeSetting, {}, (res) => {
|
||||
typesSettings.set(res)
|
||||
})
|
||||
|
||||
+2
-1
@@ -14,7 +14,7 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
import type { Product, ProductVersion } from '@hcengineering/products'
|
||||
import { FindOptions, SortingOrder } from '@hcengineering/core'
|
||||
import core, { FindOptions, SortingOrder } from '@hcengineering/core'
|
||||
import { createQuery } from '@hcengineering/presentation'
|
||||
import { Label, Loading } from '@hcengineering/ui'
|
||||
import view, { Viewlet, ViewletPreference } from '@hcengineering/view'
|
||||
@@ -45,6 +45,7 @@
|
||||
preferenceQuery.query(
|
||||
view.class.ViewletPreference,
|
||||
{
|
||||
space: core.space.Workspace,
|
||||
attachedTo: viewlet._id
|
||||
},
|
||||
(res) => {
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
import { EmployeeBox, ExpandRightDouble, UserBox } from '@hcengineering/contact-resources'
|
||||
import {
|
||||
Account,
|
||||
AccountRole,
|
||||
Class,
|
||||
Client,
|
||||
Doc,
|
||||
@@ -28,10 +29,9 @@
|
||||
Ref,
|
||||
SortingOrder,
|
||||
Space,
|
||||
Status as TaskStatus,
|
||||
fillDefaults,
|
||||
generateId,
|
||||
Status as TaskStatus,
|
||||
AccountRole,
|
||||
getCurrentAccount,
|
||||
hasAccountRole
|
||||
} from '@hcengineering/core'
|
||||
@@ -43,10 +43,10 @@
|
||||
createQuery,
|
||||
getClient
|
||||
} from '@hcengineering/presentation'
|
||||
import type { Applicant, Candidate, Vacancy } from '@hcengineering/recruit'
|
||||
import { recruitId, type Applicant, type Candidate, type Vacancy } from '@hcengineering/recruit'
|
||||
import task, { TaskType, getStates, makeRank } from '@hcengineering/task'
|
||||
import { TaskKindSelector, selectedTypeStore, typeStore } from '@hcengineering/task-resources'
|
||||
import { EmptyMarkup } from '@hcengineering/text'
|
||||
import { EmptyMarkup, isEmptyMarkup } from '@hcengineering/text'
|
||||
import ui, {
|
||||
Button,
|
||||
ColorPopup,
|
||||
@@ -132,8 +132,11 @@
|
||||
if (candidateInstance === undefined) {
|
||||
throw new Error('contact not found')
|
||||
}
|
||||
|
||||
const ops = client.apply(generateId(), recruitId + '.Create.CreateApplication')
|
||||
|
||||
if (!client.getHierarchy().hasMixin(candidateInstance, recruit.mixin.Candidate)) {
|
||||
await client.createMixin<Contact, Candidate>(
|
||||
await ops.createMixin<Contact, Candidate>(
|
||||
candidateInstance._id,
|
||||
candidateInstance._class,
|
||||
candidateInstance.space,
|
||||
@@ -144,7 +147,7 @@
|
||||
|
||||
const number = (incResult as any).object.sequence
|
||||
|
||||
await client.addCollection(
|
||||
await ops.addCollection(
|
||||
recruit.class.Applicant,
|
||||
_space,
|
||||
candidateInstance._id,
|
||||
@@ -166,11 +169,12 @@
|
||||
|
||||
await descriptionBox.createAttachments()
|
||||
|
||||
if (_comment.trim().length > 0) {
|
||||
await client.addCollection(chunter.class.ChatMessage, _space, doc._id, recruit.class.Applicant, 'comments', {
|
||||
if (_comment.trim().length > 0 && !isEmptyMarkup(_comment)) {
|
||||
await ops.addCollection(chunter.class.ChatMessage, _space, doc._id, recruit.class.Applicant, 'comments', {
|
||||
message: _comment
|
||||
})
|
||||
}
|
||||
await ops.commit()
|
||||
}
|
||||
|
||||
async function invokeValidate (
|
||||
|
||||
@@ -366,7 +366,7 @@
|
||||
const formattedSkills = (doc.skills.map((s) => s.toLowerCase()) ?? []).filter(
|
||||
(skill) => !namedElements.has(skill)
|
||||
)
|
||||
const refactoredSkills = []
|
||||
const refactoredSkills: any[] = []
|
||||
if (formattedSkills.length > 0) {
|
||||
const existingTags = Array.from(namedElements.keys()).filter((x) => x.length > 2)
|
||||
const regex = /\S+(?:[-+]\S+)+/g
|
||||
@@ -755,6 +755,7 @@
|
||||
FilePreviewPopup,
|
||||
{
|
||||
file: object.resumeUuid,
|
||||
contentType: object.resumeType,
|
||||
name: object.resumeName
|
||||
},
|
||||
object.resumeType?.startsWith('image/') ? 'centered' : 'float'
|
||||
|
||||
@@ -51,6 +51,7 @@
|
||||
preferenceQuery.query(
|
||||
view.class.ViewletPreference,
|
||||
{
|
||||
space: core.space.Workspace,
|
||||
attachedTo: viewlet._id
|
||||
},
|
||||
(res) => {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"string": {
|
||||
"ContactUs": "Contact us",
|
||||
"ReportBug": "Report a Bug?"
|
||||
"ReportBug": "Report a Bug?",
|
||||
"PrivacyPolicy": "Privacy Policy"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"string": {
|
||||
"ContactUs": "Contacta con Nosotros",
|
||||
"ReportBug": "Reportar un error?"
|
||||
"ReportBug": "Reportar un error?",
|
||||
"PrivacyPolicy": "Política de Privacidad"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"string": {
|
||||
"ContactUs": "Contactez nous",
|
||||
"ReportBug": "Rapporter un bug?"
|
||||
"ReportBug": "Rapporter un bug?",
|
||||
"PrivacyPolicy": "Politique de Confidentialité"
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"string": {
|
||||
"ContactUs": "Contate-nos",
|
||||
"ReportBug": "Reportar um erro?"
|
||||
"ReportBug": "Reportar um erro?",
|
||||
"PrivacyPolicy": "Política de Privacidade"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"string": {
|
||||
"ContactUs": "Связаться с нами",
|
||||
"ReportBug": "Сообщить об ошибке?"
|
||||
"ReportBug": "Сообщить об ошибке?",
|
||||
"PrivacyPolicy": "Политика конфиденциальности"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"string": {
|
||||
"ContactUs": "联系我们",
|
||||
"ReportBug": "报告错误?"
|
||||
"ReportBug": "报告错误?",
|
||||
"PrivacyPolicy": "隐私政策"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ export { deleteSupportConversation, updateSupportConversation } from './utils'
|
||||
export const supportLink = 'https://join.slack.com/t/hulycommunity/shared_invite/zt-2cyrevz8g-AGqEDZNsujbn4wHOWd7myg'
|
||||
export const reportBugLink = 'https://github.com/hcengineering/platform/issues/new'
|
||||
export const docsLink = 'http://docs.huly.io/'
|
||||
export const privacyPolicyLink = 'https://v1.huly.io/legal/privacy/'
|
||||
|
||||
/**
|
||||
* @public
|
||||
@@ -43,6 +44,7 @@ export default plugin(supportId, {
|
||||
},
|
||||
string: {
|
||||
ContactUs: '' as IntlString,
|
||||
ReportBug: '' as IntlString
|
||||
ReportBug: '' as IntlString,
|
||||
PrivacyPolicy: '' as IntlString
|
||||
}
|
||||
})
|
||||
|
||||
@@ -146,12 +146,15 @@
|
||||
kind={'ghost'}
|
||||
size={'large'}
|
||||
icon={show ? IconView : IconViewHide}
|
||||
dataId={`btn${show ? 'Collapse' : 'Expand'}`}
|
||||
on:click={() => {
|
||||
show = !show
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
{#if !hideAdd}<Button kind={'ghost'} size={'large'} icon={IconAdd} on:click={createTagElementPopup} />{/if}
|
||||
{#if !hideAdd}
|
||||
<Button kind={'ghost'} size={'large'} icon={IconAdd} dataId={'btnAdd'} on:click={createTagElementPopup} />
|
||||
{/if}
|
||||
</div>
|
||||
<div class="scroll">
|
||||
<div class="box">
|
||||
|
||||
@@ -120,6 +120,7 @@
|
||||
resultQuery = mergeQueries(query, e.detail)
|
||||
}}
|
||||
/>
|
||||
|
||||
<Component
|
||||
is={viewlet.$lookup.descriptor.component}
|
||||
props={{
|
||||
|
||||
@@ -129,12 +129,14 @@ export const FileExtension = FileNode.extend<FileOptions>({
|
||||
const fileId = node.attrs['file-id'] ?? ''
|
||||
if (fileId === '') return
|
||||
const fileName = node.attrs['data-file-name'] ?? ''
|
||||
const fileType = node.attrs['data-file-type'] ?? ''
|
||||
|
||||
showPopup(
|
||||
FilePreviewPopup,
|
||||
{
|
||||
file: fileId,
|
||||
name: fileName,
|
||||
contentType: fileType,
|
||||
fullSize: false,
|
||||
showIcon: false
|
||||
},
|
||||
|
||||
@@ -151,12 +151,14 @@ export const ImageExtension = ImageNode.extend<ImageOptions>({
|
||||
|
||||
const fileId = node.attrs['file-id'] ?? node.attrs.src
|
||||
const fileName = node.attrs.alt ?? ''
|
||||
const fileType = node.attrs['data-file-type'] ?? ''
|
||||
|
||||
showPopup(
|
||||
FilePreviewPopup,
|
||||
{
|
||||
file: fileId,
|
||||
name: fileName,
|
||||
contentType: fileType,
|
||||
fullSize: true,
|
||||
showIcon: false
|
||||
},
|
||||
@@ -209,10 +211,22 @@ export async function openImage (editor: Editor): Promise<void> {
|
||||
const attributes = editor.getAttributes('image')
|
||||
const fileId = attributes['file-id'] ?? attributes.src
|
||||
const fileName = attributes.alt ?? ''
|
||||
const fileType = attributes['data-file-type'] ?? ''
|
||||
await new Promise<void>((resolve) => {
|
||||
showPopup(FilePreviewPopup, { file: fileId, name: fileName, fullSize: true, showIcon: false }, 'centered', () => {
|
||||
resolve()
|
||||
})
|
||||
showPopup(
|
||||
FilePreviewPopup,
|
||||
{
|
||||
file: fileId,
|
||||
name: fileName,
|
||||
contentType: fileType,
|
||||
fullSize: true,
|
||||
showIcon: false
|
||||
},
|
||||
'centered',
|
||||
() => {
|
||||
resolve()
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -12,26 +12,26 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
import { type Class, type Space, type Doc, type Ref } from '@hcengineering/core'
|
||||
import { type Class, type Doc, type Ref, type Space } from '@hcengineering/core'
|
||||
import { getResource } from '@hcengineering/platform'
|
||||
import { getBlobRef, getClient } from '@hcengineering/presentation'
|
||||
import { CodeBlockExtension, codeBlockOptions, CodeExtension, codeOptions } from '@hcengineering/text'
|
||||
import textEditor, { type ActionContext, type ExtensionCreator, type TextEditorMode } from '@hcengineering/text-editor'
|
||||
import { type AnyExtension, type Editor, Extension } from '@tiptap/core'
|
||||
import { type Level } from '@tiptap/extension-heading'
|
||||
import ListKeymap from '@tiptap/extension-list-keymap'
|
||||
import TableHeader from '@tiptap/extension-table-header'
|
||||
import 'prosemirror-codemark/dist/codemark.css'
|
||||
import { getBlobRef, getClient } from '@hcengineering/presentation'
|
||||
import { CodeBlockExtension, codeBlockOptions, CodeExtension, codeOptions } from '@hcengineering/text'
|
||||
import textEditor, { type ActionContext, type ExtensionCreator, type TextEditorMode } from '@hcengineering/text-editor'
|
||||
|
||||
import { DefaultKit, type DefaultKitOptions } from './default-kit'
|
||||
import { HardBreakExtension } from '../components/extension/hardBreak'
|
||||
import { FileExtension, type FileOptions } from '../components/extension/fileExt'
|
||||
import { HardBreakExtension } from '../components/extension/hardBreak'
|
||||
import { ImageExtension, type ImageOptions } from '../components/extension/imageExt'
|
||||
import { NodeUuidExtension } from '../components/extension/nodeUuid'
|
||||
import { Table, TableCell, TableRow } from '../components/extension/table'
|
||||
import { SubmitExtension, type SubmitOptions } from '../components/extension/submit'
|
||||
import { ParagraphExtension } from '../components/extension/paragraph'
|
||||
import { InlineToolbarExtension } from '../components/extension/inlineToolbar'
|
||||
import { NodeUuidExtension } from '../components/extension/nodeUuid'
|
||||
import { ParagraphExtension } from '../components/extension/paragraph'
|
||||
import { SubmitExtension, type SubmitOptions } from '../components/extension/submit'
|
||||
import { Table, TableCell, TableRow } from '../components/extension/table'
|
||||
import { DefaultKit, type DefaultKitOptions } from './default-kit'
|
||||
|
||||
export interface EditorKitOptions extends DefaultKitOptions {
|
||||
history?: false
|
||||
@@ -225,7 +225,7 @@ async function buildEditorKit (): Promise<Extension<EditorKitOptions, any>> {
|
||||
inline: true,
|
||||
loadingImgSrc:
|
||||
'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4NCjxzdmcgd2lkdGg9IjMycHgiIGhlaWdodD0iMzJweCIgdmlld0JveD0iMCAwIDE2IDE2IiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPg0KICAgIDxwYXRoIGQ9Im0gNCAxIGMgLTEuNjQ0NTMxIDAgLTMgMS4zNTU0NjkgLTMgMyB2IDEgaCAxIHYgLTEgYyAwIC0xLjEwOTM3NSAwLjg5MDYyNSAtMiAyIC0yIGggMSB2IC0xIHogbSAyIDAgdiAxIGggNCB2IC0xIHogbSA1IDAgdiAxIGggMSBjIDEuMTA5Mzc1IDAgMiAwLjg5MDYyNSAyIDIgdiAxIGggMSB2IC0xIGMgMCAtMS42NDQ1MzEgLTEuMzU1NDY5IC0zIC0zIC0zIHogbSAtNSA0IGMgLTAuNTUwNzgxIDAgLTEgMC40NDkyMTkgLTEgMSBzIDAuNDQ5MjE5IDEgMSAxIHMgMSAtMC40NDkyMTkgMSAtMSBzIC0wLjQ0OTIxOSAtMSAtMSAtMSB6IG0gLTUgMSB2IDQgaCAxIHYgLTQgeiBtIDEzIDAgdiA0IGggMSB2IC00IHogbSAtNC41IDIgbCAtMiAyIGwgLTEuNSAtMSBsIC0yIDIgdiAwLjUgYyAwIDAuNSAwLjUgMC41IDAuNSAwLjUgaCA3IHMgMC40NzI2NTYgLTAuMDM1MTU2IDAuNSAtMC41IHYgLTEgeiBtIC04LjUgMyB2IDEgYyAwIDEuNjQ0NTMxIDEuMzU1NDY5IDMgMyAzIGggMSB2IC0xIGggLTEgYyAtMS4xMDkzNzUgMCAtMiAtMC44OTA2MjUgLTIgLTIgdiAtMSB6IG0gMTMgMCB2IDEgYyAwIDEuMTA5Mzc1IC0wLjg5MDYyNSAyIC0yIDIgaCAtMSB2IDEgaCAxIGMgMS42NDQ1MzEgMCAzIC0xLjM1NTQ2OSAzIC0zIHYgLTEgeiBtIC04IDMgdiAxIGggNCB2IC0xIHogbSAwIDAiIGZpbGw9IiMyZTM0MzQiIGZpbGwtb3BhY2l0eT0iMC4zNDkwMiIvPg0KPC9zdmc+DQo=',
|
||||
getBlobRef: async (file, name, size) => await getBlobRef(undefined, file, name, size),
|
||||
getBlobRef: async (file, name, size) => await getBlobRef(file, name, size),
|
||||
HTMLAttributes: this.options.image?.HTMLAttributes ?? {},
|
||||
...this.options.image
|
||||
}
|
||||
|
||||
@@ -12,14 +12,8 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
import core, {
|
||||
collaborativeDocParse,
|
||||
type Blob,
|
||||
type BlobLookup,
|
||||
type CollaborativeDoc,
|
||||
type Ref
|
||||
} from '@hcengineering/core'
|
||||
import { getBlobHref, getClient } from '@hcengineering/presentation'
|
||||
import { collaborativeDocParse, type Blob, type CollaborativeDoc, type Ref } from '@hcengineering/core'
|
||||
import { getFileUrl } from '@hcengineering/presentation'
|
||||
import { ObservableV2 as Observable } from 'lib0/observable'
|
||||
import { applyUpdate, type Doc as YDoc } from 'yjs'
|
||||
|
||||
@@ -39,11 +33,7 @@ async function fetchContent (blob: Ref<Blob>, doc: YDoc): Promise<boolean> {
|
||||
|
||||
async function fetchBlobContent (_id: Ref<Blob>): Promise<Uint8Array | undefined> {
|
||||
try {
|
||||
const blob = (await getClient().findOne(core.class.Blob, { _id })) as BlobLookup
|
||||
if (blob === undefined || blob.size === 0) {
|
||||
return undefined
|
||||
}
|
||||
const href = await getBlobHref(blob, _id)
|
||||
const href = getFileUrl(_id)
|
||||
const res = await fetch(href)
|
||||
|
||||
if (res.ok) {
|
||||
|
||||
@@ -58,6 +58,7 @@
|
||||
icon={IconChevronLeft}
|
||||
kind={'secondary'}
|
||||
size={'small'}
|
||||
dataId={'btnPrev'}
|
||||
on:click={() => {
|
||||
inc(-1)
|
||||
}}
|
||||
@@ -69,6 +70,7 @@
|
||||
type={!doubleRow ? 'type-button' : 'type-button-icon'}
|
||||
kind={'secondary'}
|
||||
size={'small'}
|
||||
dataId={'btnToday'}
|
||||
inheritFont
|
||||
hasMenu
|
||||
disabled={isToday}
|
||||
@@ -80,6 +82,7 @@
|
||||
icon={IconChevronRight}
|
||||
kind={'secondary'}
|
||||
size={'small'}
|
||||
dataId={'btnNext'}
|
||||
on:click={() => {
|
||||
inc(1)
|
||||
}}
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { DocumentQuery, Ref, Space, WithLookup } from '@hcengineering/core'
|
||||
import core, { DocumentQuery, Ref, Space, WithLookup } from '@hcengineering/core'
|
||||
import { Component } from '@hcengineering/tracker'
|
||||
import { Loading, Component as ViewComponent } from '@hcengineering/ui'
|
||||
import view, { Viewlet, ViewletPreference, ViewOptions } from '@hcengineering/view'
|
||||
@@ -34,6 +34,7 @@
|
||||
preferenceQuery.query(
|
||||
view.class.ViewletPreference,
|
||||
{
|
||||
space: core.space.Workspace,
|
||||
attachedTo: viewlet._id
|
||||
},
|
||||
(res) => {
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { Class, Doc, DocumentQuery, Ref } from '@hcengineering/core'
|
||||
import core, { Class, Doc, DocumentQuery, Ref } from '@hcengineering/core'
|
||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||
import { Issue } from '@hcengineering/tracker'
|
||||
import { Button, Chevron, ExpandCollapse, IconAdd, closeTooltip, resizeObserver, showPopup } from '@hcengineering/ui'
|
||||
@@ -77,6 +77,7 @@
|
||||
preferenceQuery.query(
|
||||
view.class.ViewletPreference,
|
||||
{
|
||||
space: core.space.Workspace,
|
||||
attachedTo: { $in: configurationRaw.map((it) => it._id) }
|
||||
},
|
||||
(res) => {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user