diff --git a/packages/frontend/core/src/components/blocksuite/block-suite-editor/bi-directional-link-panel.tsx b/packages/frontend/core/src/components/blocksuite/block-suite-editor/bi-directional-link-panel.tsx
index 0892d011e5b04..0af253c52d8e0 100644
--- a/packages/frontend/core/src/components/blocksuite/block-suite-editor/bi-directional-link-panel.tsx
+++ b/packages/frontend/core/src/components/blocksuite/block-suite-editor/bi-directional-link-panel.tsx
@@ -30,19 +30,23 @@ const BlocksuiteTextRenderer = createReactComponentFromLit({
const CollapsibleSection = ({
title,
children,
+ length,
}: {
title: ReactNode;
children: ReactNode;
+ length?: number;
}) => {
const [open, setOpen] = useState(false);
return (
{title}
-
+ {length ? (
+
+ ) : null}
{children}
@@ -152,15 +156,28 @@ export const BiDirectionalLinkPanel = () => {
}
+ length={linkGroup.links.length}
>
{linkGroup.links.map(link => {
if (!link.markdownPreview) {
return null;
}
+ const searchParams = new URLSearchParams();
+ searchParams.set('mode', link.displayMode || 'page');
+
+ // if note has displayMode === 'edgeless' && has noteBlockId,
+ // set noteBlockId as blockId
+ searchParams.set(
+ 'blockIds',
+ link.displayMode === 'edgeless' && link.noteBlockId
+ ? link.noteBlockId
+ : link.blockId
+ );
+
const to = {
pathname: '/' + linkGroup.docId,
- search: `?blockIds=${link.blockId}`,
+ search: '?' + searchParams.toString(),
hash: '',
};
return (
diff --git a/packages/frontend/core/src/modules/doc-link/entities/doc-backlinks.ts b/packages/frontend/core/src/modules/doc-link/entities/doc-backlinks.ts
index cee54f9810a42..5af4562ff8ef8 100644
--- a/packages/frontend/core/src/modules/doc-link/entities/doc-backlinks.ts
+++ b/packages/frontend/core/src/modules/doc-link/entities/doc-backlinks.ts
@@ -7,6 +7,8 @@ export interface Backlink {
docId: string;
blockId: string;
title: string;
+ noteBlockId?: string;
+ displayMode?: string;
markdownPreview?: string;
}
diff --git a/packages/frontend/core/src/modules/docs-search/schema.ts b/packages/frontend/core/src/modules/docs-search/schema.ts
index 9c8fe09ed15d7..4f59ecf8710a3 100644
--- a/packages/frontend/core/src/modules/docs-search/schema.ts
+++ b/packages/frontend/core/src/modules/docs-search/schema.ts
@@ -12,6 +12,8 @@ export type DocIndexSchema = typeof docIndexSchema;
export const blockIndexSchema = defineSchema({
docId: 'String',
blockId: 'String',
+ // note id of the block. used for focusing the note in edgeless mode
+ noteBlockId: 'String',
content: 'FullText',
flavour: 'String',
blob: 'String',
@@ -29,6 +31,8 @@ export const blockIndexSchema = defineSchema({
// { "databaseName": "xxx" }
additional: { type: 'String', index: false },
markdownPreview: { type: 'String', index: false },
+ // display mode of the block, value could be 'page' or 'edgeless' or 'both'
+ displayMode: 'String',
});
export type BlockIndexSchema = typeof blockIndexSchema;
diff --git a/packages/frontend/core/src/modules/docs-search/services/docs-search.ts b/packages/frontend/core/src/modules/docs-search/services/docs-search.ts
index 9e788eff2fb8a..7a83ba142516c 100644
--- a/packages/frontend/core/src/modules/docs-search/services/docs-search.ts
+++ b/packages/frontend/core/src/modules/docs-search/services/docs-search.ts
@@ -478,7 +478,13 @@ export class DocsSearchService extends Service {
'docId',
{
hits: {
- fields: ['docId', 'blockId', 'markdownPreview'],
+ fields: [
+ 'docId',
+ 'blockId',
+ 'noteBlockId',
+ 'markdownPreview',
+ 'displayMode',
+ ],
pagination: {
limit: 5, // the max number of backlinks to show for each doc
},
@@ -502,6 +508,9 @@ export class DocsSearchService extends Service {
return bucket.hits.nodes.map(node => {
const blockId = node.fields.blockId ?? '';
const markdownPreview = node.fields.markdownPreview ?? '';
+ const displayMode = node.fields.displayMode ?? '';
+ const noteBlockId = node.fields.noteBlockId ?? '';
+
return {
docId: bucket.key,
blockId: typeof blockId === 'string' ? blockId : blockId[0],
@@ -510,6 +519,14 @@ export class DocsSearchService extends Service {
typeof markdownPreview === 'string'
? markdownPreview
: markdownPreview[0],
+ displayMode:
+ typeof displayMode === 'string'
+ ? displayMode
+ : displayMode[0],
+ noteBlockId:
+ typeof noteBlockId === 'string'
+ ? noteBlockId
+ : noteBlockId[0],
};
});
});
diff --git a/packages/frontend/core/src/modules/docs-search/worker/in-worker.ts b/packages/frontend/core/src/modules/docs-search/worker/in-worker.ts
index 54239af69c69d..140f7559ba1c8 100644
--- a/packages/frontend/core/src/modules/docs-search/worker/in-worker.ts
+++ b/packages/frontend/core/src/modules/docs-search/worker/in-worker.ts
@@ -89,9 +89,11 @@ interface BlockDocumentInfo {
ref?: string[];
parentFlavour?: string;
parentBlockId?: string;
+ noteBlockId?: string;
additional?: { databaseName?: string };
yblock: YMap
;
markdownPreview?: string;
+ displayMode?: string;
}
const bookmarkFlavours = new Set([
@@ -260,7 +262,7 @@ function generateMarkdownPreviewBuilder(
const url = getDocLink(block.docId, databaseModel.id);
const title = databaseModel.title;
- return `[database · ${title}][](${url})\n`;
+ return `[database · ${title ?? 'Untitled'}][](${url})\n`;
};
const generateImageMarkdownPreview = (block: BlockDocumentInfo) => {
@@ -351,10 +353,7 @@ function generateMarkdownPreviewBuilder(
return `[${draftModel.name}](${draftModel.sourceId})\n`;
};
- const generateMarkdownPreview = async (
- block: BlockDocumentInfo,
- target?: BlockDocumentInfo
- ) => {
+ const generateMarkdownPreview = async (block: BlockDocumentInfo) => {
if (markdownPreviewCache.has(block)) {
return markdownPreviewCache.get(block);
}
@@ -380,18 +379,6 @@ function generateMarkdownPreviewBuilder(
markdown = trimParagraph(markdown);
}
}
-
- // only list can have indent
- if (flavour === 'affine:list' && markdown) {
- const blockDepth = getParentBlockCount(block);
- const targetDepth = target ? getParentBlockCount(target) : blockDepth;
-
- const depth = targetDepth ? blockDepth - targetDepth : 0;
- markdown = indentMarkdown(
- markdown ?? '',
- targetDepth > 0 ? depth + 1 : depth
- );
- }
} else if (flavour === 'affine:database') {
markdown = generateDatabaseMarkdownPreview(block);
} else if (
@@ -412,6 +399,12 @@ function generateMarkdownPreviewBuilder(
} else {
console.warn(`unknown flavour: ${flavour}`);
}
+
+ if (markdown) {
+ const blockDepth = getParentBlockCount(block);
+ markdown = indentMarkdown(markdown, Math.max(0, blockDepth));
+ }
+
markdownPreviewCache.set(block, markdown);
return markdown;
};
@@ -419,6 +412,18 @@ function generateMarkdownPreviewBuilder(
return generateMarkdownPreview;
}
+// find the common least indent of all lines, and remove it from all lines
+// ignore empty lines
+function unindentMarkdown(markdown: string) {
+ const lines = markdown.split('\n');
+ const minIndent = Math.min(
+ ...lines
+ .filter(line => line.trim() !== '')
+ .map(line => line.match(/^\s*/)?.[0]?.length ?? 0)
+ );
+ return lines.map(line => line.slice(minIndent)).join('\n');
+}
+
async function crawlingDocData({
docBuffer,
storageDocId,
@@ -479,10 +484,41 @@ async function crawlingDocData({
const blocks = ydoc.getMap('blocks');
+ // build a parent map for quick lookup
+ // for each block, record its parent id
+ const parentMap: Record = {};
+ for (const [id, block] of blocks.entries()) {
+ const children = block.get('sys:children') as YArray | undefined;
+ if (children instanceof YArray && children.length) {
+ for (const child of children) {
+ parentMap[child] = id;
+ }
+ }
+ }
+
if (blocks.size === 0) {
return { deletedDoc: [docId] };
}
+ // find the nearest block that satisfies the predicate
+ const nearest = (
+ blockId: string,
+ predicate: (block: YMap) => boolean
+ ) => {
+ let current: string | null = blockId;
+ while (current) {
+ const block = blocks.get(current);
+ if (block && predicate(block)) {
+ return block;
+ }
+ current = parentMap[current] ?? null;
+ }
+ return null;
+ };
+
+ const nearestByFlavour = (blockId: string, flavour: string) =>
+ nearest(blockId, block => block.get('sys:flavour') === flavour);
+
let rootBlockId: string | null = null;
for (const block of blocks.values()) {
const flavour = block.get('sys:flavour')?.toString();
@@ -528,17 +564,29 @@ async function crawlingDocData({
const flavour = block.get('sys:flavour')?.toString();
const parentFlavour = parentBlock?.get('sys:flavour')?.toString();
+ const noteBlock = nearestByFlavour(blockId, 'affine:note');
+
+ const displayMode = noteBlock?.get('sys:displayMode') || 'edgeless';
+ const noteBlockId: string | undefined = noteBlock
+ ?.get('sys:id')
+ ?.toString();
pushChildren(blockId, block);
+ const commonBlockProps = {
+ docId,
+ flavour,
+ blockId,
+ displayMode,
+ noteBlockId,
+ yblock: block,
+ };
+
if (flavour === 'affine:page') {
docTitle = block.get('prop:title').toString();
blockDocuments.push({
- docId,
- flavour,
- blockId,
+ ...commonBlockProps,
content: docTitle,
- yblock: block,
});
} else if (
flavour === 'affine:paragraph' ||
@@ -578,9 +626,7 @@ async function crawlingDocData({
: undefined;
blockDocuments.push({
- docId,
- flavour,
- blockId,
+ ...commonBlockProps,
content: text.toString(),
...refs.reduce<{ refDocId: string[]; ref: string[] }>(
(prev, curr) => {
@@ -593,7 +639,6 @@ async function crawlingDocData({
parentFlavour,
parentBlockId,
additional: { databaseName },
- yblock: block,
});
if (summaryLenNeeded > 0) {
@@ -609,14 +654,11 @@ async function crawlingDocData({
// reference info
const params = block.get('prop:params') ?? {};
blockDocuments.push({
- docId,
- flavour,
- blockId,
+ ...commonBlockProps,
refDocId: [pageId],
ref: [JSON.stringify({ docId: pageId, ...params })],
parentFlavour,
parentBlockId,
- yblock: block,
});
}
} else if (
@@ -626,13 +668,10 @@ async function crawlingDocData({
const blobId = block.get('prop:sourceId');
if (typeof blobId === 'string') {
blockDocuments.push({
- docId,
- flavour,
- blockId,
+ ...commonBlockProps,
blob: [blobId],
parentFlavour,
parentBlockId,
- yblock: block,
});
}
} else if (flavour === 'affine:surface') {
@@ -665,13 +704,10 @@ async function crawlingDocData({
}
blockDocuments.push({
- docId,
- flavour,
- blockId,
+ ...commonBlockProps,
content: texts,
parentFlavour,
parentBlockId,
- yblock: block,
});
} else if (flavour === 'affine:database') {
const texts = [];
@@ -710,26 +746,17 @@ async function crawlingDocData({
}
blockDocuments.push({
- docId,
- flavour,
- blockId,
+ ...commonBlockProps,
content: texts,
- yblock: block,
});
} else if (flavour === 'affine:latex') {
blockDocuments.push({
- docId,
- flavour,
- blockId,
+ ...commonBlockProps,
content: block.get('prop:latex')?.toString() ?? '',
- yblock: block,
});
} else if (bookmarkFlavours.has(flavour)) {
blockDocuments.push({
- docId,
- flavour,
- blockId,
- yblock: block,
+ ...commonBlockProps,
});
}
}
@@ -743,6 +770,15 @@ async function crawlingDocData({
const block = blockDocuments[i];
if (block.ref?.length) {
const target = block;
+
+ // should only generate the markdown preview belong to the same affine:note
+ const noteBlock = nearestByFlavour(block.blockId, 'affine:note');
+
+ const sameNoteBlocks = blockDocuments.filter(
+ candidate =>
+ nearestByFlavour(candidate.blockId, 'affine:note') === noteBlock
+ );
+
// only generate markdown preview for reference blocks
let previewText = (await generateMarkdownPreview(target)) ?? '';
let previousBlock = 0;
@@ -756,16 +792,16 @@ async function crawlingDocData({
previewText.length > TARGET_PREVIEW_CHARACTER || // stop if preview text reaches the limit
((previousBlock >= TARGET_PREVIOUS_BLOCK || previousIndex < 0) &&
(followBlock >= TARGET_FOLLOW_BLOCK ||
- followIndex >= blockDocuments.length))
+ followIndex >= sameNoteBlocks.length))
) // stop if no more blocks, or preview block reaches the limit
)
) {
if (previousBlock < TARGET_PREVIOUS_BLOCK) {
previousIndex--;
const block =
- previousIndex >= 0 ? blockDocuments.at(previousIndex) : null;
+ previousIndex >= 0 ? sameNoteBlocks.at(previousIndex) : null;
const markdown = block
- ? await generateMarkdownPreview(block, target)
+ ? await generateMarkdownPreview(block)
: null;
if (
markdown &&
@@ -780,9 +816,9 @@ async function crawlingDocData({
if (followBlock < TARGET_FOLLOW_BLOCK) {
followIndex++;
- const block = blockDocuments.at(followIndex);
+ const block = sameNoteBlocks.at(followIndex);
const markdown = block
- ? await generateMarkdownPreview(block, target)
+ ? await generateMarkdownPreview(block)
: null;
if (
markdown &&
@@ -796,7 +832,7 @@ async function crawlingDocData({
}
}
- block.markdownPreview = previewText;
+ block.markdownPreview = unindentMarkdown(previewText);
}
}
// #endregion
@@ -824,6 +860,8 @@ async function crawlingDocData({
? JSON.stringify(block.additional)
: undefined,
markdownPreview: block.markdownPreview,
+ displayMode: block.displayMode,
+ noteBlockId: block.noteBlockId,
})
),
},