Skip to content

Commit 266b528

Browse files
committed
feat: linked doc supports aliases (#8806)
Closes: [BS-1946](https://linear.app/affine-design/issue/BS-1946/实现-inlinecardembed-view-alias-功能) ### What's Changed! * extends `ReferenceInfo`, adding two new fields: `title` and `description` * adds `Copy` and `Edit` buttons to toolbar * `Copy`: copy link * `Edit`: custom title and description * organizes embed models and components, distinguish between external and internal
1 parent ea998b4 commit 266b528

File tree

35 files changed

+1546
-281
lines changed

35 files changed

+1546
-281
lines changed

packages/affine/block-embed/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
"@blocksuite/affine-shared": "workspace:*",
2626
"@blocksuite/block-std": "workspace:*",
2727
"@blocksuite/global": "workspace:*",
28-
"@blocksuite/icons": "^2.1.70",
28+
"@blocksuite/icons": "^2.1.75",
2929
"@blocksuite/inline": "workspace:*",
3030
"@blocksuite/store": "workspace:*",
3131
"@floating-ui/dom": "^1.6.10",

packages/affine/block-embed/src/common/render-linked-doc.ts

Lines changed: 46 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,11 @@ async function renderNoteContent(
145145
card.isNoteContentEmpty = false;
146146

147147
const noteContainer = await card.noteContainer;
148+
149+
if (!noteContainer) {
150+
return;
151+
}
152+
148153
while (noteContainer.firstChild) {
149154
noteContainer.firstChild.remove();
150155
}
@@ -199,13 +204,10 @@ async function renderNoteContent(
199204
}
200205

201206
function filterTextModel(model: BlockModel) {
202-
if (matchFlavours(model, ['affine:divider'])) {
203-
return true;
204-
}
205-
if (!matchFlavours(model, ['affine:paragraph', 'affine:list'])) {
206-
return false;
207+
if (matchFlavours(model, ['affine:paragraph', 'affine:list'])) {
208+
return !!model.text?.toString().length;
207209
}
208-
return !!model.text?.toString().length;
210+
return false;
209211
}
210212

211213
export function getNotesFromDoc(doc: Doc) {
@@ -255,3 +257,41 @@ function getSurfaceBlock(doc: Doc) {
255257
const blocks = doc.getBlocksByFlavour('affine:surface');
256258
return blocks.length !== 0 ? (blocks[0].model as SurfaceBlockModel) : null;
257259
}
260+
261+
/**
262+
* Gets the document content with a max length.
263+
*/
264+
export function getDocContentWithMaxLength(doc: Doc, maxlength = 500) {
265+
const notes = getNotesFromDoc(doc);
266+
if (!notes) return;
267+
268+
const noteChildren = notes.flatMap(note =>
269+
note.children.filter(model => filterTextModel(model))
270+
);
271+
if (!noteChildren.length) return;
272+
273+
let count = 0;
274+
let reached = false;
275+
const texts = [];
276+
277+
for (const model of noteChildren) {
278+
let t = model.text?.toString();
279+
if (t?.length) {
280+
const c: number = count + Math.max(0, texts.length - 1);
281+
282+
if (t.length + c > maxlength) {
283+
t = t.substring(0, maxlength - c);
284+
reached = true;
285+
}
286+
287+
texts.push(t);
288+
count += t.length;
289+
290+
if (reached) {
291+
break;
292+
}
293+
}
294+
}
295+
296+
return texts.join('\n');
297+
}

packages/affine/block-embed/src/embed-linked-doc-block/embed-linked-doc-block.ts

Lines changed: 37 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,13 @@ import {
2323
} from '@blocksuite/affine-shared/services';
2424
import { matchFlavours } from '@blocksuite/affine-shared/utils';
2525
import { Bound } from '@blocksuite/global/utils';
26+
import { AliasIcon } from '@blocksuite/icons/lit';
2627
import { DocCollection } from '@blocksuite/store';
2728
import { html, nothing } from 'lit';
2829
import { property, queryAsync, state } from 'lit/decorators.js';
2930
import { classMap } from 'lit/directives/class-map.js';
3031
import { styleMap } from 'lit/directives/style-map.js';
32+
import { when } from 'lit/directives/when.js';
3133

3234
import { EmbedBlockComponent } from '../common/embed-block-element.js';
3335
import { renderLinkedDocInCard } from '../common/render-linked-doc.js';
@@ -198,9 +200,7 @@ export class EmbedLinkedDocBlockComponent extends EmbedBlockComponent<EmbedLinke
198200
}
199201

200202
get docTitle() {
201-
return this.linkedDoc?.meta?.title.length
202-
? this.linkedDoc.meta.title
203-
: 'Untitled';
203+
return this.model.title || this.linkedDoc?.meta?.title || 'Untitled';
204204
}
205205

206206
get editorMode() {
@@ -322,10 +322,6 @@ export class EmbedLinkedDocBlockComponent extends EmbedBlockComponent<EmbedLinke
322322
);
323323
}
324324

325-
override disconnectedCallback() {
326-
super.disconnectedCallback();
327-
}
328-
329325
getInitialState(): {
330326
loading?: boolean;
331327
isError?: boolean;
@@ -371,17 +367,22 @@ export class EmbedLinkedDocBlockComponent extends EmbedBlockComponent<EmbedLinke
371367
? LoadingIcon
372368
: isDeleted
373369
? LinkedDocDeletedIcon
374-
: this._isLinkToNode
375-
? BlockLinkIcon
376-
: LinkedDocIcon;
370+
: this.model.title
371+
? AliasIcon({ width: '16px', height: '16pc' })
372+
: this._isLinkToNode
373+
? BlockLinkIcon
374+
: LinkedDocIcon;
375+
376+
const title = this.docTitle;
377+
const description = this.model.description;
377378

378379
const titleText = isError
379-
? linkedDoc?.meta?.title || 'Untitled'
380+
? title
380381
: isLoading
381382
? 'Loading...'
382383
: isDeleted
383384
? `Deleted doc`
384-
: linkedDoc?.meta?.title || 'Untitled';
385+
: title;
385386

386387
const showDefaultNoteContent = isError || isLoading || isDeleted || isEmpty;
387388
const defaultNoteContent = isError
@@ -409,6 +410,8 @@ export class EmbedLinkedDocBlockComponent extends EmbedBlockComponent<EmbedLinke
409410
? LinkedDocDeletedBanner
410411
: LinkedDocEmptyBanner;
411412

413+
const hasDescriptionAlias = Boolean(description && description.length > 0);
414+
412415
return this.renderEmbed(
413416
() => html`
414417
<div
@@ -431,12 +434,27 @@ export class EmbedLinkedDocBlockComponent extends EmbedBlockComponent<EmbedLinke
431434
</div>
432435
</div>
433436
434-
<div class="affine-embed-linked-doc-content-note render"></div>
435-
${showDefaultNoteContent
436-
? html`<div class="affine-embed-linked-doc-content-note default">
437-
${defaultNoteContent}
438-
</div>`
439-
: nothing}
437+
${when(
438+
hasDescriptionAlias,
439+
() =>
440+
html`<div class="affine-embed-linked-doc-content-note alias">
441+
${description}
442+
</div>`,
443+
() =>
444+
when(
445+
showDefaultNoteContent,
446+
() => html`
447+
<div class="affine-embed-linked-doc-content-note default">
448+
${defaultNoteContent}
449+
</div>
450+
`,
451+
() => html`
452+
<div
453+
class="affine-embed-linked-doc-content-note render"
454+
></div>
455+
`
456+
)
457+
)}
440458
${isError
441459
? html`
442460
<div class="affine-embed-linked-doc-card-content-reload">
@@ -518,5 +536,5 @@ export class EmbedLinkedDocBlockComponent extends EmbedBlockComponent<EmbedLinke
518536
accessor isNoteContentEmpty = false;
519537

520538
@queryAsync('.affine-embed-linked-doc-content-note.render')
521-
accessor noteContainer!: Promise<HTMLDivElement>;
539+
accessor noteContainer!: Promise<HTMLDivElement | null>;
522540
}

packages/affine/block-embed/src/embed-linked-doc-block/styles.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ export const styles = css`
8787
8888
${embedNoteContentStyles}
8989
90+
.affine-embed-linked-doc-content-note.alias,
9091
.affine-embed-linked-doc-content-note.default {
9192
flex: 1;
9293
display: -webkit-box;
@@ -105,6 +106,10 @@ export const styles = css`
105106
line-height: 20px;
106107
}
107108
109+
.affine-embed-linked-doc-content-note.alias {
110+
color: var(--affine-text-primary-color);
111+
}
112+
108113
.affine-embed-linked-doc-card-content-reload,
109114
.affine-embed-linked-doc-content-date {
110115
display: flex;

packages/affine/block-embed/src/embed-synced-doc-block/embed-synced-doc-block.ts

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,12 @@ import {
2727
} from '@blocksuite/block-std';
2828
import { GfxControllerIdentifier } from '@blocksuite/block-std/gfx';
2929
import { assertExists, Bound, getCommonBound } from '@blocksuite/global/utils';
30-
import { BlockViewType, DocCollection, type Query } from '@blocksuite/store';
30+
import {
31+
BlockViewType,
32+
DocCollection,
33+
type GetDocOptions,
34+
type Query,
35+
} from '@blocksuite/store';
3136
import { html, type PropertyValues } from 'lit';
3237
import { query, state } from 'lit/decorators.js';
3338
import { choose } from 'lit/directives/choose.js';
@@ -327,9 +332,7 @@ export class EmbedSyncedDocBlockComponent extends EmbedBlockComponent<EmbedSynce
327332
}
328333

329334
get docTitle() {
330-
return this.syncedDoc?.meta?.title.length
331-
? this.syncedDoc.meta.title
332-
: 'Untitled';
335+
return this.syncedDoc?.meta?.title || 'Untitled';
333336
}
334337

335338
get docUpdatedAt() {
@@ -353,14 +356,9 @@ export class EmbedSyncedDocBlockComponent extends EmbedBlockComponent<EmbedSynce
353356
}
354357

355358
get syncedDoc() {
356-
return this.isPageMode
357-
? this.std.collection.getDoc(this.model.pageId, {
358-
readonly: true,
359-
query: this._pageFilter,
360-
})
361-
: this.std.collection.getDoc(this.model.pageId, {
362-
readonly: true,
363-
});
359+
const options: GetDocOptions = { readonly: true };
360+
if (this.isPageMode) options.query = this._pageFilter;
361+
return this.std.collection.getDoc(this.model.pageId, options);
364362
}
365363

366364
private _checkCycle() {

packages/affine/block-embed/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ export {
2929
LinkPreviewer,
3030
type LinkPreviewResponseData,
3131
} from './common/link-previewer.js';
32+
export { getDocContentWithMaxLength } from './common/render-linked-doc.js';
3233
export { toEdgelessEmbedBlock } from './common/to-edgeless-embed-block.js';
3334

3435
export * from './embed-figma-block/index.js';

packages/affine/components/package.json

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
"@blocksuite/affine-shared": "workspace:*",
2424
"@blocksuite/block-std": "workspace:*",
2525
"@blocksuite/global": "workspace:*",
26-
"@blocksuite/icons": "^2.1.70",
26+
"@blocksuite/icons": "^2.1.75",
2727
"@blocksuite/inline": "workspace:*",
2828
"@blocksuite/store": "workspace:*",
2929
"@floating-ui/dom": "^1.6.10",
@@ -53,7 +53,8 @@
5353
"./date-picker": "./src/date-picker/index.ts",
5454
"./drag-indicator": "./src/drag-indicator/index.ts",
5555
"./virtual-keyboard": "./src/virtual-keyboard/index.ts",
56-
"./toggle-button": "./src/toggle-button/index.ts"
56+
"./toggle-button": "./src/toggle-button/index.ts",
57+
"./notification": "./src/notification/index.ts"
5758
},
5859
"publishConfig": {
5960
"access": "public",
@@ -115,6 +116,10 @@
115116
"./toggle-button": {
116117
"types": "./dist/toggle-button/index.d.ts",
117118
"import": "./dist/toggle-button/index.js"
119+
},
120+
"./notification": {
121+
"types": "./dist/notification/index.d.ts",
122+
"import": "./dist/notification/index.js"
118123
}
119124
}
120125
},
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './linked-doc.js';
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import type { BlockStdScope } from '@blocksuite/block-std';
2+
3+
import { NotificationProvider } from '@blocksuite/affine-shared/services';
4+
5+
import { toast } from '../toast/toast.js';
6+
7+
function notify(std: BlockStdScope, title: string, message: string) {
8+
const notification = std.getOptional(NotificationProvider);
9+
const { doc, host } = std;
10+
11+
if (!notification) {
12+
toast(host, title);
13+
return;
14+
}
15+
16+
const abortController = new AbortController();
17+
const clear = () => {
18+
doc.history.off('stack-item-added', addHandler);
19+
doc.history.off('stack-item-popped', popHandler);
20+
disposable.dispose();
21+
};
22+
const closeNotify = () => {
23+
abortController.abort();
24+
clear();
25+
};
26+
27+
// edit or undo or switch doc, close notify toast
28+
const addHandler = doc.history.on('stack-item-added', closeNotify);
29+
const popHandler = doc.history.on('stack-item-popped', closeNotify);
30+
const disposable = host.slots.unmounted.on(closeNotify);
31+
32+
notification.notify({
33+
title,
34+
message,
35+
accent: 'info',
36+
duration: 10 * 1000,
37+
action: {
38+
label: 'Undo',
39+
onClick: () => {
40+
doc.undo();
41+
clear();
42+
},
43+
},
44+
abort: abortController.signal,
45+
onClose: clear,
46+
});
47+
}
48+
49+
export function notifyLinkedDocSwitchedToCard(std: BlockStdScope) {
50+
notify(
51+
std,
52+
'View Updated',
53+
'The alias modification has disabled sync. The embed has been updated to a card view.'
54+
);
55+
}
56+
57+
export function notifyLinkedDocSwitchedToEmbed(std: BlockStdScope) {
58+
notify(
59+
std,
60+
'Embed View Restored',
61+
'Custom alias removed. The linked doc now displays the original title and description.'
62+
);
63+
}
64+
65+
export function notifyLinkedDocClearedAliases(std: BlockStdScope) {
66+
notify(
67+
std,
68+
'Reset successful',
69+
`Card view has been restored to original doc title and description. All custom aliases have been removed.`
70+
);
71+
}

packages/affine/components/src/rich-text/effects.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import { LatexEditorMenu } from './inline/presets/nodes/latex-node/latex-editor-
2323
import { LatexEditorUnit } from './inline/presets/nodes/latex-node/latex-editor-unit.js';
2424
import { AffineLatexNode } from './inline/presets/nodes/latex-node/latex-node.js';
2525
import { LinkPopup } from './inline/presets/nodes/link-node/link-popup/link-popup.js';
26+
import { ReferenceAliasPopup } from './inline/presets/nodes/reference-node/reference-alias-popup.js';
2627
import { ReferencePopup } from './inline/presets/nodes/reference-node/reference-popup.js';
2728
import { RichText } from './rich-text.js';
2829

@@ -35,6 +36,7 @@ export function effects() {
3536
customElements.define('link-popup', LinkPopup);
3637
customElements.define('affine-link', AffineLink);
3738
customElements.define('reference-popup', ReferencePopup);
39+
customElements.define('reference-alias-popup', ReferenceAliasPopup);
3840
customElements.define('affine-reference', AffineReference);
3941
}
4042

@@ -46,6 +48,7 @@ declare global {
4648
'affine-text': AffineText;
4749
'rich-text': RichText;
4850
'reference-popup': ReferencePopup;
51+
'reference-alias-popup': ReferenceAliasPopup;
4952
'latex-editor-unit': LatexEditorUnit;
5053
'latex-editor-menu': LatexEditorMenu;
5154
'link-popup': LinkPopup;

0 commit comments

Comments
 (0)