UBERF-9371, UBERF-9274: Apply redesign for link preview (#7893)
Some checks are pending
CI / build (push) Waiting to run
CI / svelte-check (push) Blocked by required conditions
CI / formatting (push) Blocked by required conditions
CI / test (push) Blocked by required conditions
CI / uitest (push) Waiting to run
CI / uitest-pg (push) Waiting to run
CI / uitest-qms (push) Waiting to run
CI / docker-build (push) Blocked by required conditions
CI / dist-build (push) Blocked by required conditions

This commit is contained in:
Denis Tingaikin 2025-02-05 16:01:52 +03:00 committed by GitHub
parent 65e7195e10
commit 1dd22f85c5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 153 additions and 68 deletions

View File

@ -348,6 +348,10 @@
--theme-clockface-arrows-holder: radial-gradient(at top center, #2F2F3A, #555555);
--theme-clockface-arrows-shadow: 0 0 1px white;
--theme-link-preview-bg-color: #2B2D41;
--theme-link-preview-description-color: #C1C9D6;
--theme-link-preview-text-color:#FFFFFF;
--theme-dialog-border-color: rgba(255, 255, 255, 0.1);
--theme-dialog-background-color: #2a2938;
--theme-dialog-back-color: #848484;
@ -431,6 +435,10 @@
--theme-mention-bg-color: rgba(55, 122, 230, 0.1);
--theme-mention-focused-bg-color: rgba(55, 122, 230, 0.2);
--theme-link-preview-bg-color: #E5E8F0;
--theme-link-preview-description-color: #5A667E;
--theme-link-preview-text-color:#0F121A;
--theme-trans-color: rgba(0, 0, 0, .3);
--theme-darker-color: rgba(0, 0, 0, .4);
--theme-halfcontent-color: rgba(0, 0, 0, .5);

View File

@ -25,6 +25,7 @@
export let attachments: Attachment[] | undefined = undefined
export let imageSize: AttachmentImageSize = 'auto'
export let videoPreload = true
export let isOwn = false
const query = createQuery()
const savedAttachmentsQuery = createQuery()
@ -62,4 +63,4 @@
})
</script>
<AttachmentGroup attachments={resAttachments} {savedAttachmentsIds} {imageSize} {videoPreload} />
<AttachmentGroup attachments={resAttachments} {savedAttachmentsIds} {imageSize} {videoPreload} {isOwn} />

View File

@ -24,6 +24,7 @@
export let savedAttachmentsIds: Ref<Attachment>[] = []
export let imageSize: AttachmentImageSize = 'auto'
export let videoPreload = true
export let isOwn = false
let otherAttachments: WithLookup<Attachment>[]
let linkPreviewAttachments: WithLookup<Attachment>[]
@ -47,5 +48,5 @@
<div class="gapV-2">
<AttachmentList attachments={otherAttachments} {savedAttachmentsIds} {imageSize} {videoPreload} />
<LinkPreviewList attachments={linkPreviewAttachments} />
<LinkPreviewList attachments={linkPreviewAttachments} {isOwn} />
</div>

View File

@ -128,21 +128,8 @@
return url.protocol.startsWith('http')
}
function longestSegment (s: string): string {
const segments = s.split('.')
let maxLen = segments[0].length
let result = segments[0]
for (const segment of segments) {
if (segment.length > maxLen) {
result = segment
maxLen = segment.length
}
}
return result
}
function getUrlKey (s: string): string {
const url = new URL(s)
return longestSegment(url.host) + url.pathname
return s
}
$: objectId && updateAttachments(objectId)

View File

@ -19,12 +19,13 @@
import { type Attachment } from '@hcengineering/attachment'
import { Scroller } from '@hcengineering/ui'
export let attachments: WithLookup<Attachment>[] = []
export let isOwn = false
</script>
<div class="gapV-2">
{#each attachments as attachment}
<Scroller contentDirection={'horizontal'} horizontal scrollSnap>
<LinkPreviewPresenter {attachment} />
<LinkPreviewPresenter {attachment} {isOwn} />
</Scroller>
{/each}
</div>

View File

@ -13,14 +13,17 @@
// limitations under the License.
// -->
<script lang="ts">
import { getJsonOrEmpty, type LinkPreviewDetails } from '@hcengineering/presentation'
import { getJsonOrEmpty, getClient, type LinkPreviewDetails } from '@hcengineering/presentation'
import { type Attachment } from '@hcengineering/attachment'
import { type WithLookup } from '@hcengineering/core'
import { Spinner } from '@hcengineering/ui'
import WebIcon from './icons/Web.svelte'
import { onMount } from 'svelte'
import TrashIcon from './icons/Trash.svelte'
export let attachment: WithLookup<Attachment>
export let isOwn = false
let useDefaultIcon = false
let retryCount = 0
let viewModel: LinkPreviewDetails
@ -37,7 +40,18 @@
retryCount++
previewImageSrc = `${viewModel.image}#${Date.now()}`
}
const client = getClient()
async function onDelete (): Promise<void> {
await client.removeCollection(
attachment._class,
attachment.space,
attachment._id,
attachment.attachedTo,
attachment.attachedToClass,
'attachments'
)
}
onMount(() => {
void getJsonOrEmpty(attachment.file, attachment.name)
.then((res) => {
@ -50,49 +64,61 @@
})
</script>
<div class="quote content">
<div class="content">
{#if viewModel}
<div class="gapV-2">
<div class="flex-row-center gap-1">
{#if viewModel.icon !== undefined && !useDefaultIcon}
<div class="title">
{#if viewModel.icon !== undefined && !useDefaultIcon}
<img
src={viewModel.icon}
class="preview-icon"
alt="link-preview-icon"
on:error={() => {
useDefaultIcon = true
}}
/>
{:else}
<WebIcon size="small" />
{/if}
<b><a class="link" target="_blank" href={viewModel.host}>{viewModel.hostname}</a></b>
{#if isOwn}
<!-- svelte-ignore a11y-click-events-have-key-events -->
<span
class="delete-button"
tabindex="0"
role="button"
on:click={() => {
void onDelete()
}}><TrashIcon size="small" /></span
>
{/if}
</div>
<div class="description">
{#if viewModel.title?.toLowerCase() !== viewModel.hostname?.toLowerCase()}
<div>
<b><a class="link" target="_blank" href={viewModel.url}>{viewModel.title}</a></b>
</div>
{/if}
{#if viewModel.description}
<div>
{viewModel.description}
</div>
{/if}
</div>
{#if previewImageSrc}
<div>
<a target="_blank" href={viewModel.url}>
<img
src={viewModel.icon}
class="preview-icon"
alt="link-preview-icon"
src={previewImageSrc}
class="round-image"
alt="link-preview"
on:error={() => {
useDefaultIcon = true
refreshPreviewImage()
}}
/>
{:else}
<WebIcon size="medium" />
{/if}
<b><a target="_blank" href={viewModel.host}>{viewModel.hostname}</a></b>
</a>
</div>
<div>
<div>
{#if viewModel.title?.toLowerCase() !== viewModel.hostname?.toLowerCase()}
<b><a target="_blank" href={viewModel.url}>{viewModel.title}</a></b>
{/if}
</div>
<div>
{#if viewModel.description}
{viewModel.description}
{/if}
{#if previewImageSrc}
<a target="_blank" href={viewModel.url}>
<img
src={previewImageSrc}
class="round-image"
alt="link-preview"
on:error={() => {
refreshPreviewImage()
}}
/>
</a>
{/if}
</div>
</div>
</div>
{/if}
{:else}
<div class="centered">
<Spinner size="medium" />
@ -101,23 +127,52 @@
</div>
<style lang="scss">
.delete-button {
margin-left: auto;
cursor: pointer;
}
.delete-button:not(:hover) {
color: var(--theme-link-preview-description-color);
}
.round-image {
border: 0.5px solid;
border-radius: 7px;
max-width: 25rem;
max-height: 25rem;
margin-top: 0.5rem;
border-radius: 0.375rem;
max-width: 24.5rem;
max-height: 15rem;
}
.preview-icon {
max-width: 16px;
max-height: 16px;
width: 16px;
height: 16px;
}
.quote {
border-left: 0.25rem solid;
padding-left: 0.75rem;
.link {
color: var(--theme-link-preview-text-color);
}
.title {
gap: 0.375rem;
display: flex;
flex-direction: row;
align-items: center;
}
.description {
color: var(--theme-link-preview-description-color);
}
.content span {
display: none;
}
.content:hover span {
display: block;
}
.content {
flex-direction: column;
display: flex;
line-height: 150%;
gap: 0.188rem;
padding: 0.75rem;
background-color: var(--theme-link-preview-bg-color);
border-radius: 0.75rem;
scroll-snap-align: start;
max-width: 35rem;
max-height: 35rem;
max-width: 26rem;
max-height: 28rem;
font-family: var(--font-family);
}
</style>

View File

@ -0,0 +1,32 @@
<!--
// 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.
-->
<script lang="ts">
export let size: 'tiny' | 'x-small' | 'small' | 'medium' | 'large' | 'full'
export let fill: string = 'currentColor'
</script>
<svg class="svg-{size}" {fill} viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
<path
d="M12 23C12 23.5523 12.4477 24 13 24C13.5523 24 14 23.5523 14 23V13C14 12.4477 13.5523 12 13 12C12.4477 12 12 12.4477 12 13V23Z"
/>
<path
d="M20 23C20 23.5523 19.5523 24 19 24C18.4477 24 18 23.5523 18 23V13C18 12.4477 18.4477 12 19 12C19.5523 12 20 12.4477 20 13V23Z"
/>
<path
clip-rule="evenodd"
d="M15 2C13.3431 2 12 3.34315 12 5V6H5C4.44772 6 4 6.44772 4 7C4 7.55228 4.44772 8 5 8H6V26C6 28.2091 7.79086 30 10 30H22C24.2091 30 26 28.2091 26 26V8H27C27.5523 8 28 7.55228 28 7C28 6.44772 27.5523 6 27 6H20V5C20 3.34315 18.6569 2 17 2H15ZM18 6V5C18 4.44772 17.5523 4 17 4H15C14.4477 4 14 4.44772 14 5V6H18ZM8 26V8H24V26C24 27.1046 23.1046 28 22 28H10C8.89543 28 8 27.1046 8 26Z"
/>
</svg>

View File

@ -265,7 +265,7 @@
{#if (value.attachments ?? 0) > 0 || (value.inlineButtons ?? 0) > 0}
<div class="mt-2" />
{/if}
<AttachmentDocList {value} {attachments} imageSize={attachmentImageSize} {videoPreload} />
<AttachmentDocList {value} {attachments} imageSize={attachmentImageSize} {videoPreload} {isOwn} />
<InlineButtons {value} {inlineButtons} />
</div>
</ShowMore>
@ -275,7 +275,7 @@
{#if (value.attachments ?? 0) > 0 || (value.inlineButtons ?? 0) > 0}
<div class="mt-2" />
{/if}
<AttachmentDocList {value} {attachments} imageSize={attachmentImageSize} {videoPreload} />
<AttachmentDocList {value} {attachments} imageSize={attachmentImageSize} {videoPreload} {isOwn} />
<InlineButtons {value} {inlineButtons} />
</div>
{/if}