Commit Graph

373 Commits

Author SHA1 Message Date
EYHN
e311d3d1cb feat(core): more notification type (#11209) 2025-03-27 02:10:49 +00:00
Flrande
30072da929 fix(editor): add member node before mention (#11197) 2025-03-26 08:05:02 +00:00
pengx17
61c0d01da3 feat(electron): recording popups (#11016)
Added a recording popup UI for the audio recording feature in the desktop app, improving the user experience when capturing audio from applications.

### What changed?

- Created a new popup window system for displaying recording controls
- Added a dedicated recording UI with start/stop controls and status indicators
- Moved audio encoding logic from the main app to a dedicated module
- Implemented smooth animations for popup appearance/disappearance
- Updated the recording workflow to show visual feedback during recording process
- Added internationalization support for recording-related text
- Modified the recording status flow to include new states: new, recording, stopped, ready

fix AF-2340
2025-03-26 04:53:43 +00:00
EYHN
96fa1ffe6f fix(core): fix error message missing (#11166) 2025-03-26 04:16:13 +00:00
JimmFly
014556b61f feat(core): impl invitation link (#11181)
feat(core): add invitee to getInviteInfoQuery

feat(core): enable invitation link

refactor(core): replace AcceptInviteService to InvitationService
2025-03-26 02:45:12 +00:00
Flrande
5c2bd74790 fix(editor): at menu grammar (#11167) 2025-03-25 09:20:27 +00:00
EYHN
36eb4991c9 feat(core): add more notification types (#11156) 2025-03-25 14:51:08 +08:00
fundon
d7eccd10ee chore(editor): add edgeless scribbled style feature flag (#11127)
Closes: [BS-2805](https://linear.app/affine-design/issue/BS-2805/下掉线条样式切换功能,需添加-feature-flag)
2025-03-24 12:55:00 +00:00
Flrande
4bacfbd640 feat(editor): support member node (#11075)
Close [BS-2793](https://linear.app/affine-design/issue/BS-2793/inline-member)
2025-03-24 05:57:03 +00:00
darkskygit
5acba9d5a0 feat(server): adapt context model (#11028)
expose more field in listContextObject
2025-03-21 05:36:45 +00:00
CatsJuice
e37328c83b feat(core): readwise integration tags setting (#10946)
close AF-2307, AF-2262
2025-03-20 23:20:58 +00:00
CatsJuice
f1c8a88a7c feat(core): readwise import settings ui (#10913)
close AF-2308
2025-03-20 23:20:58 +00:00
CatsJuice
e4bc43df67 feat(core): integration property ui (#10844)
close AF-2258, AF-2256
2025-03-20 23:20:57 +00:00
pengx17
fad49bb070 feat(editor): audio block (#10947)
AudioMedia entity for loading & controlling a single audio media
AudioMediaManagerService: Global audio state synchronization across tabs
AudioAttachmentService + AudioAttachmentBlock for manipulating AttachmentBlock in affine - e.g., filling transcription (using mock endpoint for now)
Added AudioBlock + AudioPlayer for rendering audio block in affine (new transcription block whose renderer is provided in affine)

fix AF-2292
fix AF-2337
2025-03-20 12:46:15 +00:00
JimmFly
d47bb64597 feat(core): add no access to share menu (#10927) 2025-03-20 07:31:11 +00:00
darkskygit
c16ae2d5b4 feat(server): audio transcription (#10733) 2025-03-20 07:12:27 +00:00
EYHN
120e193c58 feat(core): add notifications settings (#11004) 2025-03-20 03:52:56 +00:00
akumatus
ee337a16af feat(core): support collection search for ai chat (#10987)
Close [BS-2787](https://linear.app/affine-design/issue/BS-2787).
Close [BS-2788](https://linear.app/affine-design/issue/BS-2788).

![截屏2025-03-19 14.15.54.png](https://graphite-user-uploaded-assets-prod.s3.amazonaws.com/sJGviKxfE3Ap685cl5bj/573450f9-791b-4cd9-9c75-df93bf9966b4.png)
2025-03-20 00:34:12 +00:00
akumatus
47206b8d47 feat(core): support tag search for ai chat (#10965)
Close [BS-2785](https://linear.app/affine-design/issue/BS-2785).
Close [BS-2786](https://linear.app/affine-design/issue/BS-2786).

![截屏2025-03-18 21.22.34.png](https://graphite-user-uploaded-assets-prod.s3.amazonaws.com/sJGviKxfE3Ap685cl5bj/fae7216a-0402-4f14-a63c-b89f5223fc72.png)
2025-03-19 04:22:11 +00:00
JimmFly
0cb06668cd refactor(core): split the billing component into a separate file (#10924)
refactor(core): split the billing component into a separate file
feat(core): show subscription status in billing settings
![CleanShot 2025-03-17 at 17 02 59@2x](https://github.com/user-attachments/assets/4b3ee6e7-45ad-4d50-b9a5-55d658611e07)
![CleanShot 2025-03-17 at 17 00 33@2x](https://github.com/user-attachments/assets/995fd1d6-de1c-4df2-b66e-4823721adf14)
2025-03-18 10:05:45 +00:00
fengmk2
4fc3e92205 feat(server): separate storage quota limit and blob size limit (#10957)
close CLOUD-171
2025-03-18 09:28:50 +00:00
fengmk2
86c4e0705f fix(server): convert 4xx status HttpError to UserFriendlyError (#10956) 2025-03-18 09:28:50 +00:00
CatsJuice
ff8c3d1cee feat(core): intilize integration module and basic readwise impl (#10726)
close AF-2257, AF-2261, AF-2260, AF-2259

### Feat

- New `Integration` Module
- Basic Readwise integration
  - connect
  - import
  - disconnect
- Common Integration UI
- Common Integration Writer  (Transform markdown to AFFiNE Doc)

### Not Implemented

>  will be implemented in down-stack
- delete docs when disconnect
- readwise settiing UI
- integration property rendering
2025-03-18 08:13:58 +00:00
fengmk2
8880cef20b test(infra): add check job to verify committed changes (#10829) 2025-03-14 02:58:27 +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)
![CleanShot 2025-03-13 at 13 02 25@2x](https://github.com/user-attachments/assets/82f50a5b-f079-4c64-a3fa-6554735bea82)
2025-03-13 10:46:26 +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
EYHN
ea07aa8607 feat(core): add notification list (#10480) 2025-03-11 06:23:33 +00:00
CatsJuice
9f3580263a feat(core): support removing journal template from right sidebar (#10571) 2025-03-07 12:32:42 +00:00
forehalo
e02fb4fa94 refactor(core): standardize frontend error handling (#10667) 2025-03-06 13:10:18 +00:00
fundon
93920f9895 fix(editor): circular dependencies (#10661)
Closes: [BS-2766](https://linear.app/affine-design/issue/BS-2766/把-converttodatabase-移到-root-block-中,避免循环依赖)
2025-03-06 09:41:01 +00:00
Flrande
bd62634a76 feat(editor): add callout block (#10563)
- Add `CalloutBlockModel `
- Implement `CalloutBlockComponent `
- Integrate with slash menu (/)
2025-03-05 09:28:51 +00:00
EYHN
61635aa77a feat(core): add clipper import interface (#10619) 2025-03-05 04:22:03 +00:00
Saul-Mirone
fdde818ddd feat(editor): add block meta feature flag (#10548) 2025-03-03 01:45:33 +00:00
liuyi
e5e5c0a8ba perf(core): only full sync before exporting (#10408) 2025-02-25 04:41:56 +00:00
CatsJuice
8b4175c44d chore(core): update free pricing plan description (#10393) 2025-02-24 07:37:30 +00:00
Cats Juice
09cc5c392b refactor(core): new workspace selector and create dialog (#10323) 2025-02-21 17:16:38 +08:00
DarkSky
fa86f71853 feat(server): client version check (#9205)
Co-authored-by: forehalo <forehalo@gmail.com>
2025-02-20 15:50:22 +08:00
github-actions[bot]
a0e3f9909c chore(i18n): sync translations (#10184)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-02-20 02:25:19 +00:00
forehalo
02f567f2c0 chore(core): better doc action error message (#10288) 2025-02-19 10:38:38 +00:00
pengx17
78346be4fe fix(electron): update settings styles (#10193) 2025-02-18 09:54:59 +00:00
L-Sun
e639f08b71 chore(editor): remove page block feature flag (#10251) 2025-02-18 09:25:05 +00:00
forehalo
fdddaf651f chore(core): adjust selfhost sign in ui (#10226)
close AF-2227
2025-02-18 03:38:01 +00:00
JimmFly
9048b38069 fix(core): adjust share menu (#10164)
close AF-2218 AF-2215 AF-2221
2025-02-14 10:32:12 +00:00
forehalo
cc6fdef10e chore(electron): remove offline mode (#10152)
close AF-2177
2025-02-14 10:04:01 +00:00
pengx17
f20e3f6d8f feat(electron): show a warning for drop folder to split view (#10178)
fix PD-2310

![image.png](https://graphite-user-uploaded-assets-prod.s3.amazonaws.com/T2klNLEk0wxLh4NRDzhk/811df8d5-f424-4135-ad90-30135b299f12.png)
2025-02-14 06:42:56 +00:00
github-actions[bot]
e4e06f35bb chore(i18n): sync translations (#10026)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-02-12 12:18:24 +00:00
pengx17
c5c59ba00a fix(core): adjust settings names (#10061)
fix AF-2190
2025-02-11 02:14:10 +00:00
Oleg
d4f0c53a0c feat(core): frame editor settings (#9970)
Co-authored-by: L-Sun <zover.v@gmail.com>
Co-authored-by: Mirone <Saul-Mirone@outlook.com>
2025-02-10 15:23:32 +08:00
EYHN
92f4f0c2d9 feat(core): guard service (#9816) 2025-02-09 23:26:38 +00:00