Skip to content

Commit

Permalink
implement link-preview
Browse files Browse the repository at this point in the history
Signed-off-by: denis-tingaikin <[email protected]>
  • Loading branch information
denis-tingaikin committed Jan 10, 2025
1 parent 9c7ce99 commit 93c3da5
Show file tree
Hide file tree
Showing 17 changed files with 322 additions and 49 deletions.
5 changes: 3 additions & 2 deletions desktop/src/ui/platform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ import { taskId } from '@hcengineering/task'
import telegram, { telegramId } from '@hcengineering/telegram'
import { templatesId } from '@hcengineering/templates'
import tracker, { trackerId } from '@hcengineering/tracker'
import uiPlugin, { getCurrentLocation, locationStorageKeyId, locationToUrl, navigate, parseLocation, setLocationStorageKey } from '@hcengineering/ui'
import uiPlugin, { getCurrentLocation, locationStorageKeyId, navigate, setLocationStorageKey } from '@hcengineering/ui'
import { uploaderId } from '@hcengineering/uploader'
import { viewId } from '@hcengineering/view'
import workbench, { workbenchId } from '@hcengineering/workbench'
Expand Down Expand Up @@ -215,6 +215,7 @@ export async function configurePlatform (): Promise<void> {
setMetadata(presentation.metadata.PreviewConfig, parsePreviewConfig(config.PREVIEW_CONFIG))
setMetadata(presentation.metadata.UploadConfig, parseUploadConfig(config.UPLOAD_CONFIG, config.UPLOAD_URL))
setMetadata(presentation.metadata.FrontUrl, config.FRONT_URL)
setMetadata(presentation.metadata.LinkPreviewUrl, config.LINK_PREVIEW_URL ?? '')
setMetadata(presentation.metadata.StatsUrl, config.STATS_URL)

setMetadata(textEditor.metadata.Collaborator, config.COLLABORATOR ?? '')
Expand All @@ -238,7 +239,7 @@ export async function configurePlatform (): Promise<void> {
setMetadata(notification.metadata.PushPublicKey, config.PUSH_PUBLIC_KEY)

setMetadata(rekoni.metadata.RekoniUrl, config.REKONI_URL)
setMetadata(contactPlugin.metadata.LastNameFirst, myBranding.lastNameFirst === 'true' ?? false)
setMetadata(contactPlugin.metadata.LastNameFirst, myBranding.lastNameFirst === 'true')
setMetadata(love.metadata.ServiceEnpdoint, config.LOVE_ENDPOINT)
setMetadata(love.metadata.WebSocketURL, config.LIVEKIT_WS)
setMetadata(print.metadata.PrintURL, config.PRINT_URL)
Expand Down
46 changes: 23 additions & 23 deletions desktop/src/ui/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,39 +5,39 @@ import { ScreenSource } from '@hcengineering/love'
*/
export interface Config {
ACCOUNTS_URL: string
AI_URL?: string
ANALYTICS_COLLECTOR_URL?: string
BRANDING_URL?: string
CALENDAR_URL: string
COLLABORATOR?: string
COLLABORATOR_URL: string
FRONT_URL: string
CONFIG_URL: string
DESKTOP_UPDATES_CHANNEL?: string
DESKTOP_UPDATES_URL?: string
DISABLE_SIGNUP?: string
FILES_URL: string
UPLOAD_URL: string
MODEL_VERSION?: string
VERSION?: string
TELEGRAM_URL: string
GMAIL_URL: string
CALENDAR_URL: string
REKONI_URL: string
INITIAL_URL: string
FRONT_URL: string
GITHUB_APP: string
GITHUB_CLIENTID: string
GITHUB_URL: string
CONFIG_URL: string
LOVE_ENDPOINT?: string
GMAIL_URL: string
INITIAL_URL: string
LINK_PREVIEW_URL?: string
LIVEKIT_WS?: string
SIGN_URL?: string
LOVE_ENDPOINT?: string
MODEL_VERSION?: string
PRESENCE_URL?: string
PREVIEW_CONFIG: string
PRINT_URL?: string
PUSH_PUBLIC_KEY: string
ANALYTICS_COLLECTOR_URL?: string
AI_URL?:string
DISABLE_SIGNUP?: string
BRANDING_URL?: string
PREVIEW_CONFIG: string
UPLOAD_CONFIG: string
DESKTOP_UPDATES_URL?: string
DESKTOP_UPDATES_CHANNEL?: string
TELEGRAM_BOT_URL?: string
PRESENCE_URL?: string

REKONI_URL: string
SIGN_URL?: string
STATS_URL?: string
TELEGRAM_BOT_URL?: string
TELEGRAM_URL: string
UPLOAD_CONFIG: string
UPLOAD_URL: string
VERSION?: string
}

export interface Branding {
Expand Down
2 changes: 0 additions & 2 deletions packages/presentation/src/drawing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,9 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//

export interface DrawingData {
content?: string
}

export interface DrawingProps {
readonly: boolean
autoSize?: boolean
Expand Down
18 changes: 18 additions & 0 deletions packages/presentation/src/file.ts
Original file line number Diff line number Diff line change
Expand Up @@ -284,3 +284,21 @@ async function uploadFileWithSignedUrl (file: File, uuid: string, uploadUrl: str
})
}
}

export async function fetchJson (file: string, name: string): Promise<Blob | undefined> {
const resp = await fetch(
getFileUrl(file, name),
{ signal: AbortSignal.timeout(5 * 1000) }
)
if (!resp.ok) {
console.error({ error: `failed to process request: ${resp.status}` })
return undefined
}
try {
return await resp.json()
} catch (err) {
const message = err instanceof Error ? err.message : String(err)
console.error({ error: 'failed to parse json:' + message })
}
return undefined
}
1 change: 1 addition & 0 deletions packages/presentation/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,4 @@ export * from './preview'
export * from './sound'
export * from './stats'
export * from './drawing'
export * from './link-preview'
57 changes: 57 additions & 0 deletions packages/presentation/src/link-preview.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
//
// Copyright © 2025 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
//
import { getMetadata } from '@hcengineering/platform'
import presentation from './plugin'

export function isLinkPreviewEnabled (): boolean {
return getMetadata(presentation.metadata.LinkPreviewUrl) !== undefined
}
export interface LinkPreviewDetails {
title?: string
description?: string
url?: string
icon?: string
image?: string
charset?: string
hostname?: string
host?: string
}

export function canDisplayLinkPreview (val: LinkPreviewDetails): boolean {
if (val.hostname === undefined && val.title === undefined) {
return false
}
if (val.image === undefined && val.description === undefined) {
return false
}
return true
}

export async function fetchLinkPreviewDetails (url: string): Promise<LinkPreviewDetails> {
try {
const linkPreviewUrl = getMetadata(presentation.metadata.LinkPreviewUrl)
const response = await fetch(`${linkPreviewUrl}?q=${url}`, {
signal: AbortSignal.timeout(5 * 1000)
})
if (!response.ok) {
throw new Error(`status: ${response.status}`)
}
console.log(response)
return response.json() as LinkPreviewDetails
} catch (error) {
console.error(`An error occurced on fetching or parsing data by ${url}, error:`, error)
return {}
}
}
1 change: 1 addition & 0 deletions packages/presentation/src/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ export default plugin(presentationId, {
Workspace: '' as Metadata<string>,
WorkspaceId: '' as Metadata<string>,
FrontUrl: '' as Asset,
LinkPreviewUrl: '' as Metadata<string>,
UploadConfig: '' as Metadata<UploadConfig>,
PreviewConfig: '' as Metadata<PreviewConfig | undefined>,
ClientHook: '' as Metadata<ClientHook>,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
-->
<script lang="ts">
import { Attachment } from '@hcengineering/attachment'
import core, { type Doc, type Ref, type WithLookup } from '@hcengineering/core'
import { type Doc, type Ref, type WithLookup } from '@hcengineering/core'
import { createQuery } from '@hcengineering/presentation'
import attachment from '../plugin'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
function openAttachment (): void {
showAttachmentPreviewPopup(value)
}
$: src = getFileUrl(value.file, value.name)
</script>

Expand Down
13 changes: 13 additions & 0 deletions plugins/attachment-resources/src/components/AttachmentList.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,25 @@
{#if attachments.length}
<Scroller contentDirection={'horizontal'} horizontal gap={'gap-3'} scrollSnap>
{#each attachments as attachment}
{#if attachment !== undefined && attachment.type !== 'application/link-preview'}
<AttachmentPreview
value={attachment}
isSaved={savedAttachmentsIds?.includes(attachment._id) ?? false}
{imageSize}
{videoPreload}
/>
{/if}
{/each}
</Scroller>
{#each attachments as attachment}
{#if attachment !== undefined && attachment.type === 'application/link-preview'}
<br>
<AttachmentPreview
value={attachment}
isSaved={savedAttachmentsIds?.includes(attachment._id) ?? false}
{imageSize}
{videoPreload}
/>
{/if}
{/each}
{/if}
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,17 @@
getBlobRef,
getFileUrl,
previewTypes,
fetchJson,
sizeToWidth
} from '@hcengineering/presentation'
import { Label } from '@hcengineering/ui'
import { permissionsStore } from '@hcengineering/view-resources'
import filesize from 'filesize'
import { createEventDispatcher } from 'svelte'
import { getType, openAttachmentInSidebar, showAttachmentPreviewPopup } from '../utils'
import { type LinkPreviewDetails } from '@hcengineering/presentation'
import AttachmentName from './AttachmentName.svelte'
import Spinner from '@hcengineering/ui/src/components/Spinner.svelte'
export let value: WithLookup<Attachment> | undefined
export let removable: boolean = false
Expand All @@ -39,7 +41,7 @@
const dispatch = createEventDispatcher()
const maxLenght: number = 30
const maxLenght: number = 20
const trimFilename = (fname: string): string =>
fname.length > maxLenght ? fname.substr(0, (maxLenght - 1) / 2) + '...' + fname.substr(-(maxLenght - 1) / 2) : fname
Expand Down Expand Up @@ -83,6 +85,16 @@
await openAttachmentInSidebar(value)
}
}
async function fetchLinkPreviewDetails (): Promise<LinkPreviewDetails> {
if (value === undefined) {
return {}
}
try {
return await fetchJson(value.file, value.name) as LinkPreviewDetails
} catch {
return {}
}
}
function middleClickHandler (e: MouseEvent): void {
if (e.button !== 1) return
Expand All @@ -106,7 +118,7 @@
<AttachmentName {value} />
{:else}
<div class="flex-row-center attachment-container">
{#if value}
{#if value !== undefined && value.type !== 'application/link-preview'}
{#await getBlobRef(value.file, value.name, sizeToWidth('large')) then valueRef}
<a
class="no-line"
Expand Down Expand Up @@ -165,6 +177,41 @@
</div>
</div>
{/await}
{:else if value !== undefined && value.type === 'application/link-preview'}
{#await fetchLinkPreviewDetails() }
<Spinner size='small'/>
{:then linkPreviewDetails }
<div class="flex-center icon">
{#if linkPreviewDetails.icon}
<img src="{linkPreviewDetails.icon}" width="32" height="32" alt="link-preview">
{:else}
URL
{/if}
</div>
<div class="flex-col info-container">
<div class="name">
<a target="_blank" class="no-line" style:flex-shrink={0} href="{linkPreviewDetails.url}">{trimFilename(linkPreviewDetails?.title ?? value.name)}</a>
</div>
<div class="info-content flex-row-center">
<span class="actions inline-flex clear-mins ml-1 gap-1">
{#if linkPreviewDetails.description}
{trimFilename(linkPreviewDetails.description)}
<span>•</span>
{/if}
<span
class="remove-link"
on:click={(ev) => {
ev.stopPropagation()
ev.preventDefault()
dispatch('remove', value)
}}
>
<Label label={presentation.string.Delete} />
</span>
</span>
</div>
</div>
{/await}
{/if}
</div>
{/if}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@
import { ListSelectionProvider } from '@hcengineering/view-resources'
import { createEventDispatcher } from 'svelte'
import { WithLookup } from '@hcengineering/core'
import { AttachmentImageSize } from '../types'
import { getType, showAttachmentPreviewPopup } from '../utils'
import AttachmentActions from './AttachmentActions.svelte'
import AttachmentImagePreview from './AttachmentImagePreview.svelte'
import LinkPreviewPresenter from './LinkPreviewPresenter.svelte'
import AttachmentPresenter from './AttachmentPresenter.svelte'
import AttachmentVideoPreview from './AttachmentVideoPreview.svelte'
import AudioPlayer from './AudioPlayer.svelte'
Expand All @@ -37,9 +37,11 @@
const dispatch = createEventDispatcher()
$: type = getType(value.type)
</script>
{#if type === 'image'}
</script>
{#if type === 'link-preview'}
<LinkPreviewPresenter attachment={value}/>
{:else if type === 'image'}
<!-- svelte-ignore a11y-click-events-have-key-events -->
<!-- svelte-ignore a11y-no-static-element-interactions -->
<div
Expand Down Expand Up @@ -107,8 +109,8 @@
}
.content {
max-width: 20rem;
max-height: 20rem;
max-width: 25rem;
max-height: 25rem;
scroll-snap-align: start;
}
</style>
Loading

0 comments on commit 93c3da5

Please sign in to comment.