snapshot of https://github.com/toeverything/AFFiNE.git (canary, 2026-09-16; blobs > 131072B omitted)
Some checks failed
Build & Test / Analyze (javascript, blocksuite) (push) Failing after 3s
Build & Test / Rust test filter (push) Failing after 3s
Build & Test / E2E BlockSuite Test (2) (push) Failing after 3s
Build & Test / E2E BlockSuite Test (3) (push) Failing after 4s
Build & Test / E2E BlockSuite Test (4) (push) Failing after 3s
Build & Test / E2E BlockSuite Cross Browser Test (push) Failing after 3s
Build & Test / E2E Mobile Test (1) (push) Failing after 3s
Build & Test / E2E Mobile Test (2) (push) Failing after 3s
Build & Test / Build AFFiNE native (x86_64-unknown-linux-gnu) (push) Failing after 4s
Build & Test / Unit Test (1) (push) Has been skipped
Build & Test / Unit Test (2) (push) Has been skipped
Build & Test / Unit Test (3) (push) Has been skipped
Build & Test / Build Server native (push) Failing after 3s
Build & Test / Check Git Status (push) Has been skipped
Build & Test / Server Test (0, 4) (push) Has been skipped
Build & Test / Server Test (1, 4) (push) Has been skipped
Build & Test / Analyze (javascript, affine) (push) Failing after 3s
Build & Test / Analyze (typescript, affine) (push) Failing after 3s
Build & Test / Analyze (typescript, blocksuite) (push) Failing after 3s
Build & Test / Mobile native build filter (push) Failing after 3s
Build & Test / Check yarn binary (push) Failing after 3s
Build & Test / E2E BlockSuite Test (1) (push) Failing after 4s
Build & Test / E2E BlockSuite Test (5) (push) Failing after 3s
Build & Test / Unit Test (4) (push) Has been skipped
Build & Test / Unit Test (5) (push) Has been skipped
Build & Test / Native Unit Test (push) Has been skipped
Build & Test / Server Test (2, 4) (push) Has been skipped
Build & Test / Server Test (3, 4) (push) Has been skipped
Build & Test / Server Test with Elasticsearch (push) Has been skipped
Build & Test / Server Test with Embedded Search (push) Has been skipped
Build & Test / Server Test with Manticore Search (push) Has been skipped
Build & Test / Server E2E Test (push) Has been skipped
Build & Test / Cloud Desktop E2E Test (push) Has been skipped
Build & Test / Cloud E2E Test 1/2 (push) Has been skipped
Build & Test / Cloud E2E Test 2/2 (push) Has been skipped
Build & Test / Build @affine/electron renderer (push) Failing after 3s
Build & Test / Copilot test filter (push) Failing after 4s
Build & Test / Lint Rust (push) Has been skipped
Build & Test / Build Android app (push) Has been skipped
Build & Test / Test Android app (push) Has been skipped
Build & Test / Run native tests (push) Has been skipped
Build & Test / Server Copilot Api Test (push) Has been skipped
Build & Test / Frontend Copilot E2E Test (3, 5) (push) Has been skipped
Build & Test / Frontend Copilot E2E Test (4, 5) (push) Has been skipped
Build & Test / Frontend Copilot E2E Test (1, 5) (push) Has been skipped
Build & Test / Frontend Copilot E2E Test (2, 5) (push) Has been skipped
Build & Test / Frontend Copilot E2E Test (5, 5) (push) Has been skipped
Build & Test / Desktop Test (ubuntu-latest, linux, x64, x86_64-unknown-linux-gnu, true) (push) Has been cancelled
Build & Test / Desktop Test (windows-latest, windows, x64, x86_64-pc-windows-msvc, true) (push) Has been cancelled
Build & Test / 3, 2, 1 Launch (push) Has been cancelled
Build & Test / Lint (push) Has been cancelled
Build & Test / Typecheck (push) Has been cancelled
Build & Test / Build iOS app (push) Has been cancelled
Build & Test / Build AFFiNE native (aarch64-apple-darwin) (push) Has been cancelled
Build & Test / E2E Test (1) (push) Has been cancelled
Build & Test / E2E Test (2) (push) Has been cancelled
Build & Test / E2E Test (3) (push) Has been cancelled
Build & Test / E2E Test (4) (push) Has been cancelled
Build & Test / E2E Test (5) (push) Has been cancelled
Build & Test / Build AFFiNE native (x86_64-apple-darwin) (push) Has been cancelled
Build & Test / Build AFFiNE native (aarch64-pc-windows-msvc) (push) Has been cancelled
Build & Test / Build AFFiNE native (x86_64-pc-windows-msvc) (push) Has been cancelled
Build & Test / Desktop Test (macos-latest, macos, arm64, aarch64-apple-darwin, true) (push) Has been cancelled
Build & Test / Desktop Test (macos-latest, macos, x64, x86_64-apple-darwin, false) (push) Has been cancelled
Some checks failed
Build & Test / Analyze (javascript, blocksuite) (push) Failing after 3s
Build & Test / Rust test filter (push) Failing after 3s
Build & Test / E2E BlockSuite Test (2) (push) Failing after 3s
Build & Test / E2E BlockSuite Test (3) (push) Failing after 4s
Build & Test / E2E BlockSuite Test (4) (push) Failing after 3s
Build & Test / E2E BlockSuite Cross Browser Test (push) Failing after 3s
Build & Test / E2E Mobile Test (1) (push) Failing after 3s
Build & Test / E2E Mobile Test (2) (push) Failing after 3s
Build & Test / Build AFFiNE native (x86_64-unknown-linux-gnu) (push) Failing after 4s
Build & Test / Unit Test (1) (push) Has been skipped
Build & Test / Unit Test (2) (push) Has been skipped
Build & Test / Unit Test (3) (push) Has been skipped
Build & Test / Build Server native (push) Failing after 3s
Build & Test / Check Git Status (push) Has been skipped
Build & Test / Server Test (0, 4) (push) Has been skipped
Build & Test / Server Test (1, 4) (push) Has been skipped
Build & Test / Analyze (javascript, affine) (push) Failing after 3s
Build & Test / Analyze (typescript, affine) (push) Failing after 3s
Build & Test / Analyze (typescript, blocksuite) (push) Failing after 3s
Build & Test / Mobile native build filter (push) Failing after 3s
Build & Test / Check yarn binary (push) Failing after 3s
Build & Test / E2E BlockSuite Test (1) (push) Failing after 4s
Build & Test / E2E BlockSuite Test (5) (push) Failing after 3s
Build & Test / Unit Test (4) (push) Has been skipped
Build & Test / Unit Test (5) (push) Has been skipped
Build & Test / Native Unit Test (push) Has been skipped
Build & Test / Server Test (2, 4) (push) Has been skipped
Build & Test / Server Test (3, 4) (push) Has been skipped
Build & Test / Server Test with Elasticsearch (push) Has been skipped
Build & Test / Server Test with Embedded Search (push) Has been skipped
Build & Test / Server Test with Manticore Search (push) Has been skipped
Build & Test / Server E2E Test (push) Has been skipped
Build & Test / Cloud Desktop E2E Test (push) Has been skipped
Build & Test / Cloud E2E Test 1/2 (push) Has been skipped
Build & Test / Cloud E2E Test 2/2 (push) Has been skipped
Build & Test / Build @affine/electron renderer (push) Failing after 3s
Build & Test / Copilot test filter (push) Failing after 4s
Build & Test / Lint Rust (push) Has been skipped
Build & Test / Build Android app (push) Has been skipped
Build & Test / Test Android app (push) Has been skipped
Build & Test / Run native tests (push) Has been skipped
Build & Test / Server Copilot Api Test (push) Has been skipped
Build & Test / Frontend Copilot E2E Test (3, 5) (push) Has been skipped
Build & Test / Frontend Copilot E2E Test (4, 5) (push) Has been skipped
Build & Test / Frontend Copilot E2E Test (1, 5) (push) Has been skipped
Build & Test / Frontend Copilot E2E Test (2, 5) (push) Has been skipped
Build & Test / Frontend Copilot E2E Test (5, 5) (push) Has been skipped
Build & Test / Desktop Test (ubuntu-latest, linux, x64, x86_64-unknown-linux-gnu, true) (push) Has been cancelled
Build & Test / Desktop Test (windows-latest, windows, x64, x86_64-pc-windows-msvc, true) (push) Has been cancelled
Build & Test / 3, 2, 1 Launch (push) Has been cancelled
Build & Test / Lint (push) Has been cancelled
Build & Test / Typecheck (push) Has been cancelled
Build & Test / Build iOS app (push) Has been cancelled
Build & Test / Build AFFiNE native (aarch64-apple-darwin) (push) Has been cancelled
Build & Test / E2E Test (1) (push) Has been cancelled
Build & Test / E2E Test (2) (push) Has been cancelled
Build & Test / E2E Test (3) (push) Has been cancelled
Build & Test / E2E Test (4) (push) Has been cancelled
Build & Test / E2E Test (5) (push) Has been cancelled
Build & Test / Build AFFiNE native (x86_64-apple-darwin) (push) Has been cancelled
Build & Test / Build AFFiNE native (aarch64-pc-windows-msvc) (push) Has been cancelled
Build & Test / Build AFFiNE native (x86_64-pc-windows-msvc) (push) Has been cancelled
Build & Test / Desktop Test (macos-latest, macos, arm64, aarch64-apple-darwin, true) (push) Has been cancelled
Build & Test / Desktop Test (macos-latest, macos, x64, x86_64-apple-darwin, false) (push) Has been cancelled
This commit is contained in:
277
blocksuite/affine/components/src/link-preview/details.ts
Normal file
277
blocksuite/affine/components/src/link-preview/details.ts
Normal file
@@ -0,0 +1,277 @@
|
||||
import type {
|
||||
LinkPreviewProvider,
|
||||
LinkPreviewResult,
|
||||
} from '@blocksuite/affine-shared/services';
|
||||
import { unsafeCSSVarV2 } from '@blocksuite/affine-shared/theme';
|
||||
import { ToggleDownIcon, ToggleRightIcon } from '@blocksuite/icons/lit';
|
||||
import { flip, offset, shift } from '@floating-ui/dom';
|
||||
import { css, html, LitElement, nothing, type PropertyValues } from 'lit';
|
||||
import { property, state } from 'lit/decorators.js';
|
||||
|
||||
import { createLitPortal } from '../portal';
|
||||
|
||||
export class LinkPreviewDetails extends LitElement {
|
||||
static override styles = css`
|
||||
:host {
|
||||
display: block;
|
||||
color: ${unsafeCSSVarV2('text/primary')};
|
||||
font: 13px/20px var(--affine-font-family);
|
||||
}
|
||||
:host([floating]) {
|
||||
position: absolute;
|
||||
right: 8px;
|
||||
bottom: 8px;
|
||||
}
|
||||
:host([floating]) button {
|
||||
background: ${unsafeCSSVarV2('layer/background/primary')};
|
||||
box-shadow: 0 0 0 1px ${unsafeCSSVarV2('layer/background/tertiary')};
|
||||
}
|
||||
.content.floating {
|
||||
margin: 0;
|
||||
width: min(360px, calc(100vw - 32px));
|
||||
box-sizing: border-box;
|
||||
box-shadow: var(--affine-menu-shadow);
|
||||
}
|
||||
button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
border: 0;
|
||||
border-radius: 4px;
|
||||
padding: 4px 8px;
|
||||
background: transparent;
|
||||
color: ${unsafeCSSVarV2('text/secondary')};
|
||||
font: inherit;
|
||||
cursor: pointer;
|
||||
}
|
||||
button:hover {
|
||||
background: var(--affine-hover-color);
|
||||
}
|
||||
button:focus-visible {
|
||||
outline: 2px solid var(--affine-primary-color);
|
||||
}
|
||||
.content {
|
||||
color: inherit;
|
||||
margin-top: 4px;
|
||||
padding: 12px;
|
||||
border: 1px solid ${unsafeCSSVarV2('layer/background/tertiary')};
|
||||
border-radius: 8px;
|
||||
background: ${unsafeCSSVarV2('layer/background/primary')};
|
||||
max-height: 320px;
|
||||
overflow: auto;
|
||||
user-select: text;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
.meta,
|
||||
.status,
|
||||
time {
|
||||
color: ${unsafeCSSVarV2('text/secondary')};
|
||||
}
|
||||
.segment {
|
||||
margin-top: 8px;
|
||||
}
|
||||
.segment-text,
|
||||
.description {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
h4 {
|
||||
margin: 12px 0 4px;
|
||||
font: inherit;
|
||||
font-weight: 600;
|
||||
}
|
||||
time {
|
||||
margin-right: 8px;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
`;
|
||||
|
||||
@property({ attribute: false }) accessor url = '';
|
||||
@property({ type: Boolean, reflect: true }) accessor floating = false;
|
||||
@property({ attribute: false }) accessor provider!: LinkPreviewProvider;
|
||||
@state() private accessor open = false;
|
||||
@state() private accessor loading = false;
|
||||
@state() private accessor value: LinkPreviewResult | undefined;
|
||||
private controller?: AbortController;
|
||||
private popupController?: AbortController;
|
||||
private updatePopup?: () => void;
|
||||
|
||||
protected override willUpdate(changed: PropertyValues) {
|
||||
if (changed.has('url') || changed.has('provider')) {
|
||||
this.controller?.abort();
|
||||
this.open = false;
|
||||
this.loading = false;
|
||||
this.value = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
override disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
this.popupController?.abort();
|
||||
this.controller?.abort();
|
||||
this.open = false;
|
||||
this.loading = false;
|
||||
}
|
||||
|
||||
protected override updated() {
|
||||
if (!this.floating || !this.open) {
|
||||
this.popupController?.abort();
|
||||
return;
|
||||
}
|
||||
if (this.popupController) {
|
||||
this.updatePopup?.();
|
||||
return;
|
||||
}
|
||||
const button = this.shadowRoot?.querySelector('button');
|
||||
if (!button) return;
|
||||
const controller = new AbortController();
|
||||
this.popupController = controller;
|
||||
controller.signal.addEventListener('abort', () => {
|
||||
this.popupController = undefined;
|
||||
this.updatePopup = undefined;
|
||||
this.open = false;
|
||||
});
|
||||
document.addEventListener(
|
||||
'keydown',
|
||||
event => {
|
||||
if (event.key !== 'Escape') return;
|
||||
event.stopPropagation();
|
||||
controller.abort();
|
||||
button.focus();
|
||||
},
|
||||
{ signal: controller.signal }
|
||||
);
|
||||
const { portal } = createLitPortal({
|
||||
template: ({ updatePortal }) => {
|
||||
this.updatePopup = updatePortal;
|
||||
return html`<style>
|
||||
${LinkPreviewDetails.styles.cssText}</style
|
||||
>${this.renderContent()}`;
|
||||
},
|
||||
computePosition: {
|
||||
referenceElement: button,
|
||||
placement: 'bottom-end',
|
||||
middleware: [offset(8), flip(), shift({ padding: 16 })],
|
||||
autoUpdate: { animationFrame: true },
|
||||
},
|
||||
abortController: controller,
|
||||
positionStrategy: 'fixed',
|
||||
closeOnClickAway: true,
|
||||
});
|
||||
portal.shadowRoot?.querySelector<HTMLElement>('.content')?.focus();
|
||||
}
|
||||
|
||||
private async toggle() {
|
||||
this.open = !this.open;
|
||||
if (!this.open || this.loading || this.value) return;
|
||||
const controller = new AbortController();
|
||||
this.controller = controller;
|
||||
this.loading = true;
|
||||
try {
|
||||
const value = await this.provider.query(this.url, controller.signal, [
|
||||
'transcript',
|
||||
]);
|
||||
if (!controller.signal.aborted) this.value = value;
|
||||
} catch {
|
||||
if (!controller.signal.aborted) this.value = {};
|
||||
} finally {
|
||||
if (this.controller === controller) this.loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
override render() {
|
||||
return html`<div
|
||||
contenteditable="false"
|
||||
@click=${(event: Event) => event.stopPropagation()}
|
||||
@dblclick=${(event: Event) => event.stopPropagation()}
|
||||
@pointerdown=${(event: Event) => event.stopPropagation()}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
aria-expanded=${this.open}
|
||||
aria-controls=${this.floating ? nothing : 'details'}
|
||||
aria-haspopup=${this.floating ? 'dialog' : nothing}
|
||||
@click=${this.toggle}
|
||||
>
|
||||
${(this.open ? ToggleDownIcon : ToggleRightIcon)({ width: '16px', height: '16px' })}
|
||||
Details
|
||||
</button>
|
||||
${this.open && !this.floating ? this.renderContent() : nothing}
|
||||
</div>`;
|
||||
}
|
||||
|
||||
private renderContent() {
|
||||
const value = this.value;
|
||||
const transcript = value?.transcript;
|
||||
const metadata = [
|
||||
value?.author?.name,
|
||||
value?.publishedAt?.split('T')[0],
|
||||
value?.durationSeconds === undefined
|
||||
? undefined
|
||||
: timestamp(value.durationSeconds),
|
||||
].filter(Boolean);
|
||||
let chapterIndex = 0;
|
||||
const chapters = transcript?.chapters ?? [];
|
||||
return html`<div
|
||||
class="content ${this.floating ? 'floating' : ''}"
|
||||
id="details"
|
||||
contenteditable="false"
|
||||
aria-busy=${this.loading}
|
||||
role=${this.floating ? 'dialog' : 'region'}
|
||||
aria-label="Link details"
|
||||
tabindex="-1"
|
||||
@click=${(event: Event) => event.stopPropagation()}
|
||||
@dblclick=${(event: Event) => event.stopPropagation()}
|
||||
@pointerdown=${(event: Event) => event.stopPropagation()}
|
||||
@wheel=${(event: Event) => event.stopPropagation()}
|
||||
>
|
||||
${
|
||||
this.loading
|
||||
? html`<span class="status" role="status">Loading details…</span>`
|
||||
: html`
|
||||
${metadata.length ? html`<div class="meta">${metadata.join(' · ')}</div>` : nothing}
|
||||
${value?.description ? html`<p class="description">${value.description}</p>` : nothing}
|
||||
${
|
||||
transcript?.segments.length
|
||||
? html`
|
||||
<h4>Transcript</h4>
|
||||
${transcript.segments.map(segment => {
|
||||
const headings = [];
|
||||
while (
|
||||
chapterIndex < chapters.length &&
|
||||
chapters[chapterIndex].startSeconds <=
|
||||
(segment.startSeconds ?? 0)
|
||||
) {
|
||||
headings.push(
|
||||
html`<h4>${chapters[chapterIndex++].title}</h4>`
|
||||
);
|
||||
}
|
||||
return html`${headings}
|
||||
<div class="segment">
|
||||
${segment.startSeconds === undefined ? nothing : html`<time>${timestamp(segment.startSeconds)}</time>`}${segment.speaker ? html`<strong>${segment.speaker}: </strong>` : nothing}<span
|
||||
class="segment-text"
|
||||
>${segment.text}</span
|
||||
>
|
||||
</div>`;
|
||||
})}
|
||||
${transcript.truncated ? html`<p class="status">Transcript truncated by the preview service.</p>` : nothing}
|
||||
`
|
||||
: html`<div class="status">No transcript available.</div>`
|
||||
}
|
||||
`
|
||||
}
|
||||
</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
function timestamp(seconds: number) {
|
||||
const minutes = Math.floor(seconds / 60);
|
||||
return `${minutes}:${Math.floor(seconds % 60)
|
||||
.toString()
|
||||
.padStart(2, '0')}`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'affine-link-preview-details': LinkPreviewDetails;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user