darkskygit
8af0526a22
chore(infra): update dev env template ( #10845 )
2025-03-14 04:55:27 +00:00
akumatus
daccb2c865
feat(core): add ai file context api ( #10842 )
...
Close [BS-2349](https://linear.app/affine-design/issue/BS-2349 ).
### What Changed?
- Add file context graphql apis
- Pass matched file chunks to LLM
[录屏2025-02-19 23.27.47.mov <span class="graphite__hidden">(uploaded via Graphite)</span> <img class="graphite__hidden" src="https://app.graphite.dev/api/v1/graphite/video/thumbnail/sJGviKxfE3Ap685cl5bj/8e8a98ca-6959-4bb6-9759-b51d97cede49.mov " />](https://app.graphite.dev/media/video/sJGviKxfE3Ap685cl5bj/8e8a98ca-6959-4bb6-9759-b51d97cede49.mov )
2025-03-14 04:29:54 +00:00
fengmk2
8880cef20b
test(infra): add check job to verify committed changes ( #10829 )
2025-03-14 02:58:27 +00:00
yoyoyohamapi
e086fd2a43
refactor(editor): getFirstContentBlock -> getFirstBlock & getLastContentBlock -> getLastBlock ( #10809 )
2025-03-14 02:35:22 +00:00
yoyoyohamapi
d3aae962bc
test(editor): getFirstContentBlock & getLastContentBlock & isNothingSelected command ( #10757 )
...
### TL;DR
Added unit tests for block and selection commands, along with a new test helper system for creating test documents.
### What changed?
- Added unit tests for several commands:
- `getFirstContentBlockCommand`
- `getLastContentBlockCommand`
- `isNothingSelectedCommand`
- Created a new test helpers make it easier to create structured test documents with a html-like syntax:
```typescript
import { describe, expect, it } from 'vitest';
import { affine } from '../__tests__/utils/affine-template';
describe('My Test', () => {
it('should correctly handle document structure', () => {
const doc = affine`
<affine-page>
<affine-note>
<affine-paragraph>Test content</affine-paragraph>
</affine-note>
</affine-page>
`;
// Get blocks
const pages = doc.getBlocksByFlavour('affine:page');
const notes = doc.getBlocksByFlavour('affine:note');
const paragraphs = doc.getBlocksByFlavour('affine:paragraph');
expect(pages.length).toBe(1);
expect(notes.length).toBe(1);
expect(paragraphs.length).toBe(1);
// Perform more tests here...
});
});
```
2025-03-14 02:35:21 +00:00
yoyoyohamapi
04efca362e
feat(editor): is nothing selected command ( #10721 )
...
### TL;DR
Added a new command to check if nothing is currently selected in the editor.
### What changed?
- Created new `isNothingSelectedCommand` to determine if there are no active selections
2025-03-14 02:35:21 +00:00
yoyoyohamapi
aa15b106d9
feat(editor): content block getter command ( #10720 )
...
### TL;DR
Added new commands to retrieve the first and last content blocks in a document.
### What changed?
- Created `getFirstContentBlockCommand` to find the first content block in a document
- Created `getLastContentBlockCommand` to find the last content block in a document
- Added `getFirstNoteBlock` utility function to find the first note block in a document
2025-03-14 02:35:20 +00:00
forehalo
d936553047
chore: auto assign feature reqeusts ( #10819 )
2025-03-14 02:20:14 +00:00
fundon
b238aa3182
fix(editor): clamp method in color picker ( #10840 )
...
Related to https://github.com/toeverything/AFFiNE/pull/10577
2025-03-13 19:37:37 +00:00
fundon
341321284e
fix(core): should not invert label when its shape text ( #10765 )
...
Closes: [BS-2797](https://linear.app/affine-design/issue/BS-2797/在-settings-preview-中,无需对-shape-text-颜色-label-进行反转 )
Releated: https://github.com/toeverything/AFFiNE/pull/10546
2025-03-13 13:15:41 +00:00
Boshen
8783859dd1
chore: bump oxlint to v0.15.15; enable import/named ( #10836 )
2025-03-13 20:59:59 +08:00
darkskygit
d8373f66e7
feat(server): context awareness for copilot ( #9611 )
...
fix PD-2167
fix PD-2169
fix PD-2190
2025-03-13 11:44:55 +00:00
Saul-Mirone
05f3069efd
feat(editor): add i18n support for block meta display ( #10831 )
2025-03-13 11:28:56 +00:00
JimmFly
0c9591f08e
feat(core): add an entry for admin panel ( #10813 )
...

2025-03-13 10:46:26 +00:00
JimmFly
7df06ea98b
feat(admin): add server version check ( #10816 )
2025-03-13 18:45:56 +08:00
JimmFly
bed4074bdb
feat(admin): add import and export users to admin panel ( #10810 )
2025-03-13 18:45:17 +08:00
JimmFly
e96302ccb2
feat(admin): add ban user to admin panel ( #10780 )
2025-03-13 18:44:13 +08:00
pengx17
d24ced3dbd
fix(electron): electron fallback container should not contain tabs header ( #10826 )
...

fix double tabs header issue when loading from external url
2025-03-13 10:14:26 +00:00
JimmFly
21aa47c094
feat(admin): make the left navigation bar collapsable ( #10774 )
2025-03-13 09:57:10 +00:00
zzj3720
a4608b52f2
fix(editor): database block e2e test flaky ( #10828 )
2025-03-13 09:40:24 +00:00
fengmk2
9518ebee95
chore(server): add pro user for testing with extended workspace member limit ( #10827 )
2025-03-13 09:11:59 +00:00
EYHN
a903f8685b
fix(infra): use framework stack provider ( #10825 )
...
Move the stack logic from react hook to FrameworkStackProvider,
now `frameworkProvider.get(ServerService)` is equal with `useService(ServerService)`
2025-03-13 08:56:05 +00:00
forehalo
7100d87efe
chore(core): doc role telemetry ( #10822 )
2025-03-13 08:15:46 +00:00
L-Sun
7ba1c1b271
chore(editor): use at menu to insert linked doc ( #10808 )
...
Close [BS-2799](https://linear.app/affine-design/issue/BS-2799/区分linked-doc入口 )
This PR removes the patch for `Linked Doc` action in the slash menu via the `QuickSearch`, reverting it to use the at menu for inserting.
2025-03-13 08:01:59 +00:00
doodlewind
0f062b7157
refactor(editor): make turbo renderer a gfx extension ( #10818 )
...
This allows for easier debugging via `gfx.turboRenderer`
2025-03-13 07:45:05 +00:00
pengx17
a6fd0a135b
fix(electron): add back updater ( #10814 )
2025-03-13 07:29:49 +00:00
Saul-Mirone
7f45993fdb
feat(editor): add ui for display block meta in toolbar ( #10817 )
2025-03-13 07:06:27 +00:00
Saul-Mirone
5148e67891
feat(editor): improve block meta updated event handler ( #10815 )
2025-03-13 06:34:03 +00:00
L-Sun
8ac687628c
chore(editor): at menu stays open when left right arrow keys are pressed ( #10806 )
...
Close [BS-2644](https://linear.app/affine-design/issue/BS-2644/menu-support )
2025-03-13 06:14:35 +00:00
forehalo
3db3db0bbc
chore(server): revert doc write restriction ( #10807 )
2025-03-13 05:51:19 +00:00
Saul-Mirone
250f3f1efd
feat(editor): add isLocal flag in blockUpdated subject ( #10799 )
2025-03-13 05:33:06 +00:00
doodlewind
c023b724d0
refactor(editor): generic layout type support for turbo renderer ( #10766 )
...
This PR refactored the turbo renderer architecture to support multiple block layout types.
- New base class `BlockLayoutPainter` and `BlockLayoutProvider` are introduced for writing extendable per-block layout querying and painting logic.
- Paragraph-specific lines are all moved into dedicated classes (`ParagraphLayoutProvider` and `ParagraphLayoutPainter`) under the `/variants/paragraph` dir.
- The `renderer-utils.ts` doesn't contain paragraph-specific logic now.
- The `text-utils.ts` is also now scoped for paragraph only.
- Worker messages are now strongly typed.
Upcoming PR should further implement the block registration system using extension API. The `variants` dir could still exist, since there will be similar rendering logic that can be reused among block types (i.e., between paragraph block and list block).
2025-03-13 05:18:12 +00:00
forehalo
c1c750d782
fix(server): failed to resolve user importing result ( #10804 )
2025-03-13 05:02:58 +00:00
pengx17
844b13af1f
fix(core): unsub in LiveData.fromSignal ( #10756 )
2025-03-13 04:47:23 +00:00
akumatus
21d850deeb
feat(core): add tag-chip and collection-chip lit components ( #10795 )
...
Close [BS-2790](https://linear.app/affine-design/issue/BS-2790 ).

2025-03-13 04:26:58 +00:00
donteatfriedrice
d2c62602a4
feat(editor): support embed iframe block ( #10740 )
...
To close:
[BS-2660](https://linear.app/affine-design/issue/BS-2660/slash-menu-支持-iframe-embed )
[BS-2661](https://linear.app/affine-design/issue/BS-2661/iframe-embed-block-model-and-block-component )
[BS-2662](https://linear.app/affine-design/issue/BS-2662/iframe-embed-block-toolbar )
[BS-2768](https://linear.app/affine-design/issue/BS-2768/iframe-embed-block-loading-和-error-态 )
[BS-2670](https://linear.app/affine-design/issue/BS-2670/iframe-embed-block-导出 )
# PR Description
# Add Embed Iframe Block Support
## Overview
This PR introduces a new `EmbedIframeBlock` to enhance content embedding capabilities within our editor. This block allows users to seamlessly embed external content from various providers (Google Drive, Spotify, etc.) directly into their docs.
## New Blocks
### EmbedIframeBlock
The core block that renders embedded iframe content. This block:
* Displays external content within a secure iframe
* Handles loading states with visual feedback
* Provides error handling with edit and retry options
* Supports customization of width, height, and other iframe attributes
### Supporting Components
* **EmbedIframeCreateModal**: Modal interface for creating new iframe embeds
* **EmbedIframeLinkEditPopup**: UI for editing existing embed links
* **EmbedIframeLoadingCard**: Visual feedback during content loading
* **EmbedIframeErrorCard**: Error handling with retry functionality
## New Store Extensions
### EmbedIframeConfigExtension
This extension provides configuration for different embed providers:
```typescript
/**
* The options for the iframe
* @example
* {
* defaultWidth: '100%',
* defaultHeight: '152px',
* style: 'border-radius: 8px;',
* allow: 'autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture',
* }
* =>
* <iframe
* width="100%"
* height="152px"
* style="border-radius: 8px;"
* allow="autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture"
* ></iframe>
*/
export type IframeOptions = {
defaultWidth?: string;
defaultHeight?: string;
style?: string;
referrerpolicy?: string;
scrolling?: boolean;
allow?: string;
allowFullscreen?: boolean;
};
/**
* Define the config of an embed iframe block provider
*/
export type EmbedIframeConfig = {
/**
* The name of the embed iframe block provider
*/
name: string;
/**
* The function to match the url
*/
match: (url: string) => boolean;
/**
* The function to build the oEmbed URL for fetching embed data
*/
buildOEmbedUrl: (url: string) => string | undefined;
/**
* Use oEmbed URL directly as iframe src without fetching oEmbed data
*/
useOEmbedUrlDirectly: boolean;
/**
* The options for the iframe
*/
options?: IframeOptions;
};
export const EmbedIframeConfigIdentifier =
createIdentifier<EmbedIframeConfig>('EmbedIframeConfig');
export function EmbedIframeConfigExtension(
config: EmbedIframeConfig
): ExtensionType & {
identifier: ServiceIdentifier<EmbedIframeConfig>;
} {
const identifier = EmbedIframeConfigIdentifier(config.name);
return {
setup: di => {
di.addImpl(identifier, () => config);
},
identifier,
};
}
```
**example:**
```typescript
// blocksuite/affine/blocks/block-embed/src/embed-iframe-block/configs/providers/spotify.ts
const SPOTIFY_DEFAULT_WIDTH = '100%';
const SPOTIFY_DEFAULT_HEIGHT = '152px';
// https://developer.spotify.com/documentation/embeds/reference/oembed
const spotifyEndpoint = 'https://open.spotify.com/oembed ';
const spotifyUrlValidationOptions: EmbedIframeUrlValidationOptions = {
protocols: ['https:'],
hostnames: ['open.spotify.com', 'spotify.link'],
};
const spotifyConfig = {
name: 'spotify',
match: (url: string) =>
validateEmbedIframeUrl(url, spotifyUrlValidationOptions),
buildOEmbedUrl: (url: string) => {
const match = validateEmbedIframeUrl(url, spotifyUrlValidationOptions);
if (!match) {
return undefined;
}
const encodedUrl = encodeURIComponent(url);
const oEmbedUrl = `${spotifyEndpoint}?url=${encodedUrl}`;
return oEmbedUrl;
},
useOEmbedUrlDirectly: false,
options: {
defaultWidth: SPOTIFY_DEFAULT_WIDTH,
defaultHeight: SPOTIFY_DEFAULT_HEIGHT,
allow:
'autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture',
style: 'border-radius: 12px;',
allowFullscreen: true,
},
};
// add the config extension to store
export const SpotifyEmbedConfig = EmbedIframeConfigExtension(spotifyConfig);
```
**Key features:**
* Provider registration and discovery
* URL pattern matching
* Provider-specific embed options (width, height, features)
### EmbedIframeService
This service provides abilities to handle URL validation, data fetching, and block creation
**Type:**
```typescript
/**
* Service for handling embeddable URLs
*/
export interface EmbedIframeProvider {
/**
* Check if a URL can be embedded
* @param url URL to check
* @returns true if the URL can be embedded, false otherwise
*/
canEmbed: (url: string) => boolean;
/**
* Build a API URL for fetching embed data
* @param url URL to build API URL
* @returns API URL if the URL can be embedded, undefined otherwise
*/
buildOEmbedUrl: (url: string) => string | undefined;
/**
* Get the embed iframe config
* @param url URL to get embed iframe config
* @returns Embed iframe config if the URL can be embedded, undefined otherwise
*/
getConfig: (url: string) => EmbedIframeConfig | undefined;
/**
* Get embed iframe data
* @param url URL to get embed iframe data
* @returns Embed iframe data if the URL can be embedded, undefined otherwise
*/
getEmbedIframeData: (url: string) => Promise<EmbedIframeData | null>;
/**
* Parse an embeddable URL and add an EmbedIframeBlock to doc
* @param url Original url to embed
* @param parentId Parent block ID
* @param index Optional index to insert at
* @returns Created block id if successful, undefined if the URL cannot be embedded
*/
addEmbedIframeBlock: (
props: Partial<EmbedIframeBlockProps>,
parentId: string,
index?: number
) => string | undefined;
}
```
**Implemetation:**
```typescript
export class EmbedIframeService
extends StoreExtension
implements EmbedIframeProvider
{
static override key = 'embed-iframe-service';
private readonly _configs: EmbedIframeConfig[];
constructor(store: Store) {
super(store);
this._configs = Array.from(
store.provider.getAll(EmbedIframeConfigIdentifier).values()
);
}
canEmbed = (url: string): boolean => {
return this._configs.some(config => config.match(url));
};
buildOEmbedUrl = (url: string): string | undefined => {
return this._configs.find(config => config.match(url))?.buildOEmbedUrl(url);
};
getConfig = (url: string): EmbedIframeConfig | undefined => {
return this._configs.find(config => config.match(url));
};
getEmbedIframeData = async (
url: string,
signal?: AbortSignal
): Promise<EmbedIframeData | null> => {
try {
const config = this._configs.find(config => config.match(url));
if (!config) {
return null;
}
const oEmbedUrl = config.buildOEmbedUrl(url);
if (!oEmbedUrl) {
return null;
}
// if the config useOEmbedUrlDirectly is true, return the url directly as iframe_url
if (config.useOEmbedUrlDirectly) {
return {
iframe_url: oEmbedUrl,
};
}
// otherwise, fetch the oEmbed data
const response = await fetch(oEmbedUrl, { signal });
if (!response.ok) {
console.warn(
`Failed to fetch oEmbed data: ${response.status} ${response.statusText}`
);
return null;
}
const data = await response.json();
return data as EmbedIframeData;
} catch (error) {
if (error instanceof Error && error.name !== 'AbortError') {
console.error('Error fetching embed iframe data:', error);
}
return null;
}
};
addEmbedIframeBlock = (
props: Partial<EmbedIframeBlockProps>,
parentId: string,
index?: number
): string | undefined => {
const blockId = this.store.addBlock(
'affine:embed-iframe',
props,
parentId,
index
);
return blockId;
};
}
```
**Usage:**
```typescript
// Usage example
const embedIframeService = this.std.get(EmbedIframeService);
// Check if a URL can be embedded
const canEmbed = embedIframeService.canEmbed(url);
// Get embed data for a URL
const embedData = await embedIframeService.getEmbedIframeData(url);
// Add an embed iframe block to the document
const block = embedIframeService.addEmbedIframeBlock({
url,
iframeUrl: embedData.iframe_url,
title: embedData.title,
description: embedData.description
}, parentId, index);
```
**Key features:**
* URL validation and transformation
* Provider-specific data fetching
* Block creation and management
## Adaptations
### Toolbar Integration
Added toolbar actions for embedded content:
* Copy link
* Edit embed title and description
* Toggle between inline/card views
* Add caption
* And more
### Slash Menu Integration
Added a new slash menu option for embedding content:
* Embed item for inserting embed iframe block
* Conditional rendering based on feature flags
### Adapters
Implemented adapters for various formats:
* **HTML Adapter**: Exports embed original urls as html links
* **Markdown Adapter**: Exports embed original urls as markdown links
* **Plain Text Adapter**: Exports embed original urls as link text
## To Be Continued:
- [ ] **UI Optimization**
- [ ] **Edgeless Mode Support**
- [ ] **Mobile Support**
2025-03-13 04:11:46 +00:00
akumatus
98a3cf8516
feat(core): update blocksuite icons ( #10805 )
2025-03-13 03:55:55 +00:00
zzj3720
f6a62fa737
fix(editor): clicking the sorting button results in an error ( #10800 )
2025-03-13 03:17:48 +00:00
EYHN
86729fb447
feat(core): adjust web clipper page ( #10779 )
v0.21.0-canary.1
2025-03-13 10:59:50 +08:00
fundon
5ed8541cb1
fix(editor): should directly return the sub-action content if it exists ( #10778 )
2025-03-12 16:54:02 +00:00
darkskygit
514a5fc3a9
feat(server): update deploy config for context ( #10431 )
2025-03-12 11:59:46 +00:00
fengmk2
aa3bfb0a05
fix(server): only return workspace user fields ( #10700 )
...
close CLOUD-164
2025-03-12 10:35:00 +00:00
forehalo
d8ebf7b3c5
fix(core): wrong top margin of local workspace hint in setting panel ( #10782 )
...
close AF-2243
2025-03-12 10:16:47 +00:00
fengmk2
3417cc5dc1
fix(core): handle Content-Type with charset in fetch error handling ( #10777 )
2025-03-12 09:56:41 +00:00
zzj3720
01151ec18f
refactor(editor): add runtime type checks to database cell values ( #10770 )
2025-03-12 09:22:41 +00:00
fengmk2
fd3ce431fe
fix(core): assert app schema url on open-app ( #10687 )
2025-03-12 08:42:35 +00:00
darkskygit
c3b407041e
chore(core): extend workflow timeout ( #10760 )
2025-03-12 08:26:34 +00:00
fengmk2
43712839fd
refactor(server): improve magic link login flow ( #10736 )
2025-03-12 15:27:36 +08:00
fengmk2
867ae7933f
refactor(server): improve oauth login flow ( #10648 )
...
close CLOUD-145
2025-03-12 15:27:36 +08:00
fundon
d823792f85
refactor(editor): simplify color picker ( #10776 )
...
### What's Changed!
* Added `enableCustomColor` property into `EdgelessColorPickerButton` component
* Removed redundant code
2025-03-12 05:17:04 +00:00