Some checks failed
Build & Test / Analyze (javascript, affine) (push) Failing after 4s
Build & Test / Analyze (typescript, affine) (push) Failing after 3s
Build & Test / Rust test filter (push) Failing after 4s
Build & Test / Check yarn binary (push) Failing after 3s
Build & Test / E2E BlockSuite Test (3) (push) Failing after 3s
Build & Test / Analyze (javascript, blocksuite) (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 / E2E BlockSuite Test (1) (push) Failing after 4s
Build & Test / E2E BlockSuite Test (2) (push) Failing after 4s
Build & Test / E2E Mobile Test (1) (push) Failing after 3s
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 / 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 / E2E BlockSuite Test (4) (push) Failing after 3s
Build & Test / E2E BlockSuite Test (5) (push) Failing after 3s
Build & Test / E2E BlockSuite Cross Browser Test (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 3s
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 / 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 / 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 / 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 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 (1, 5) (push) Has been skipped
Build & Test / Frontend Copilot E2E Test (2, 5) (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 (5, 5) (push) Has been skipped
Release / Canary Gate (push) Has been skipped
Build & Test / Build AFFiNE native (aarch64-apple-darwin) (push) Has been cancelled
Build & Test / Lint (push) Has been cancelled
Build & Test / Typecheck (push) Has been cancelled
Build & Test / Build AFFiNE native (x86_64-apple-darwin) (push) Has been cancelled
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 / 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 (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
Build & Test / 3, 2, 1 Launch (push) Has been cancelled
Build & Test / Build iOS app (push) Has been cancelled
Copilot Test Automatically / Setup Test (push) Failing after 1s
Release / Prepare (push) Failing after 2s
Release / Release Cloud (push) Has been skipped
Release / Release Desktop (push) Has been skipped
Release / Release Docker Image (push) Has been skipped
Release / Release Mobile (push) Has been skipped
214 lines
5.7 KiB
TypeScript
214 lines
5.7 KiB
TypeScript
import { getNotesFromDoc } from '@blocksuite/affine-block-embed';
|
|
import { ViewExtensionManagerIdentifier } from '@blocksuite/affine-ext-loader';
|
|
import {
|
|
ImageBlockModel,
|
|
ListBlockModel,
|
|
ParagraphBlockModel,
|
|
} from '@blocksuite/affine-model';
|
|
import { EMBED_CARD_HEIGHT } from '@blocksuite/affine-shared/consts';
|
|
import { matchModels } from '@blocksuite/affine-shared/utils';
|
|
import { BlockStdScope } from '@blocksuite/std';
|
|
import type { BlockModel, Query } from '@blocksuite/store';
|
|
import { render, type TemplateResult } from 'lit';
|
|
|
|
import type { EmbedLinkedDocBlockComponent } from '../embed-linked-doc-block';
|
|
import type { EmbedSyncedDocCard } from '../embed-synced-doc-block/components/embed-synced-doc-card';
|
|
|
|
export function renderLinkedDocInCard(
|
|
card: EmbedLinkedDocBlockComponent | EmbedSyncedDocCard
|
|
) {
|
|
const linkedDoc = card.linkedDoc;
|
|
if (!linkedDoc) {
|
|
console.error(
|
|
`Trying to load page ${card.model.props.pageId} in linked page block, but the page is not found.`
|
|
);
|
|
return;
|
|
}
|
|
|
|
// oxlint-disable-next-line sonarjs/no-collapsible-if
|
|
if ('bannerContainer' in card) {
|
|
if (card.editorMode === 'page') {
|
|
renderPageAsBanner(card).catch(e => {
|
|
console.error(e);
|
|
card.isError = true;
|
|
});
|
|
}
|
|
}
|
|
|
|
renderNoteContent(card).catch(e => {
|
|
console.error(e);
|
|
card.isError = true;
|
|
});
|
|
}
|
|
|
|
async function renderPageAsBanner(card: EmbedSyncedDocCard) {
|
|
const linkedDoc = card.linkedDoc;
|
|
if (!linkedDoc) {
|
|
console.error(
|
|
`Trying to load page ${card.model.props.pageId} in linked page block, but the page is not found.`
|
|
);
|
|
return;
|
|
}
|
|
|
|
const notes = getNotesFromDoc(linkedDoc);
|
|
if (!notes) {
|
|
card.isBannerEmpty = true;
|
|
return;
|
|
}
|
|
|
|
const target = notes.flatMap(note =>
|
|
note.children.filter(child => matchModels(child, [ImageBlockModel]))
|
|
)[0];
|
|
|
|
if (target) {
|
|
await renderImageAsBanner(card, target);
|
|
return;
|
|
}
|
|
|
|
card.isBannerEmpty = true;
|
|
}
|
|
|
|
async function renderImageAsBanner(
|
|
card: EmbedSyncedDocCard,
|
|
image: BlockModel
|
|
) {
|
|
const sourceId = (image as ImageBlockModel).props.sourceId;
|
|
if (!sourceId) return;
|
|
|
|
const storage = card.linkedDoc?.blobSync;
|
|
if (!storage) return;
|
|
|
|
const blob = await storage.get(sourceId);
|
|
if (!blob) return;
|
|
|
|
const url = URL.createObjectURL(blob);
|
|
const $img = document.createElement('img');
|
|
$img.src = url;
|
|
await addCover(card, $img);
|
|
|
|
card.isBannerEmpty = false;
|
|
}
|
|
|
|
async function addCover(
|
|
card: EmbedSyncedDocCard,
|
|
cover: HTMLElement | TemplateResult<1>
|
|
) {
|
|
const coverContainer = await card.bannerContainer;
|
|
if (!coverContainer) return;
|
|
while (coverContainer.firstChild) {
|
|
coverContainer.firstChild.remove();
|
|
}
|
|
|
|
if (cover instanceof HTMLElement) {
|
|
coverContainer.append(cover);
|
|
} else {
|
|
render(cover, coverContainer);
|
|
}
|
|
}
|
|
|
|
async function renderNoteContent(
|
|
card: EmbedLinkedDocBlockComponent | EmbedSyncedDocCard
|
|
) {
|
|
card.isNoteContentEmpty = true;
|
|
|
|
const doc = card.linkedDoc;
|
|
if (!doc) {
|
|
console.error(
|
|
`Trying to load page ${card.model.props.pageId} in linked page block, but the page is not found.`
|
|
);
|
|
return;
|
|
}
|
|
|
|
const notes = getNotesFromDoc(doc);
|
|
if (!notes) {
|
|
return;
|
|
}
|
|
|
|
const cardStyle = card.model.props.style;
|
|
const isHorizontal = cardStyle === 'horizontal';
|
|
const allowFlavours = isHorizontal ? [] : [ImageBlockModel];
|
|
|
|
const noteChildren = notes.flatMap(note =>
|
|
note.children.filter(model => {
|
|
if (matchModels(model, allowFlavours)) {
|
|
return true;
|
|
}
|
|
return filterTextModel(model);
|
|
})
|
|
);
|
|
|
|
if (!noteChildren.length) {
|
|
return;
|
|
}
|
|
|
|
card.isNoteContentEmpty = false;
|
|
|
|
const noteContainer = await card.noteContainer;
|
|
|
|
if (!noteContainer) {
|
|
return;
|
|
}
|
|
|
|
while (noteContainer.firstChild) {
|
|
noteContainer.firstChild.remove();
|
|
}
|
|
|
|
const noteBlocksContainer = document.createElement('div');
|
|
noteBlocksContainer.classList.add('affine-embed-doc-content-note-blocks');
|
|
noteBlocksContainer.contentEditable = 'false';
|
|
noteContainer.append(noteBlocksContainer);
|
|
|
|
if (isHorizontal) {
|
|
// When the card is horizontal, we only render the first block
|
|
noteChildren.splice(1);
|
|
} else {
|
|
// Before rendering, we can not know the height of each block
|
|
// But we can limit the number of blocks to render simply by the height of the card
|
|
const cardHeight = EMBED_CARD_HEIGHT[cardStyle];
|
|
const minSingleBlockHeight = 20;
|
|
const maxBlockCount = Math.floor(cardHeight / minSingleBlockHeight);
|
|
if (noteChildren.length > maxBlockCount) {
|
|
noteChildren.splice(maxBlockCount);
|
|
}
|
|
}
|
|
const childIds = noteChildren.map(child => child.id);
|
|
const ids: string[] = [];
|
|
childIds.forEach(block => {
|
|
let parent: string | null = block;
|
|
while (parent && !ids.includes(parent)) {
|
|
ids.push(parent);
|
|
parent = doc.getParent(parent)?.id ?? null;
|
|
}
|
|
});
|
|
const query: Query = {
|
|
mode: 'strict',
|
|
match: ids.map(id => ({ id, viewType: 'display' })),
|
|
};
|
|
const previewDoc = doc.doc.getStore({ query });
|
|
const std = card.host.std;
|
|
const previewSpec = std
|
|
.get(ViewExtensionManagerIdentifier)
|
|
.get('preview-page');
|
|
const previewStd = new BlockStdScope({
|
|
store: previewDoc,
|
|
extensions: previewSpec,
|
|
});
|
|
const previewTemplate = previewStd.render();
|
|
const fragment = document.createDocumentFragment();
|
|
render(previewTemplate, fragment);
|
|
noteBlocksContainer.append(fragment);
|
|
const contentEditableElements = noteBlocksContainer.querySelectorAll(
|
|
'[contenteditable="true"]'
|
|
);
|
|
contentEditableElements.forEach(element => {
|
|
(element as HTMLElement).contentEditable = 'false';
|
|
});
|
|
}
|
|
|
|
function filterTextModel(model: BlockModel) {
|
|
if (matchModels(model, [ParagraphBlockModel, ListBlockModel])) {
|
|
return !!model.text?.toString().length;
|
|
}
|
|
return false;
|
|
}
|