feat(editor): add feature flag service (#9592)

This commit is contained in:
Saul-Mirone committed 2025-01-08 15:46:31 +00:00
1 parent fd26b72255
commit 3683297ccf
55 files changed
+283 -188

No files matched your search

@@ -1,4 +1,5 @@
import type { DatabaseBlockModel } from '@blocksuite/affine-model'; import type { DatabaseBlockModel } from '@blocksuite/affine-model';
import { FeatureFlagService } from '@blocksuite/affine-shared/services';
import { import {
insertPositionToIndex, insertPositionToIndex,
type InsertToPosition, type InsertToPosition,
@@ -57,10 +58,12 @@ export class DatabaseBlockDataSource extends DataSourceBase {
private readonly _model: DatabaseBlockModel; private readonly _model: DatabaseBlockModel;
override featureFlags$: ReadonlySignal<DatabaseFlags> = computed(() => { override featureFlags$: ReadonlySignal<DatabaseFlags> = computed(() => {
const featureFlagService = this.doc.get(FeatureFlagService);
const flag = featureFlagService.getFlag(
'enable_database_number_formatting'
);
return { return {
enable_number_formatting: enable_number_formatting: flag ?? false,
this.doc.awarenessStore.getFlag('enable_database_number_formatting') ??
false,
}; };
}); });
@@ -3,6 +3,7 @@ import {
EMBED_CARD_HEIGHT, EMBED_CARD_HEIGHT,
EMBED_CARD_WIDTH, EMBED_CARD_WIDTH,
} from '@blocksuite/affine-shared/consts'; } from '@blocksuite/affine-shared/consts';
import { FeatureFlagService } from '@blocksuite/affine-shared/services';
import { cloneReferenceInfoWithoutAliases } from '@blocksuite/affine-shared/utils'; import { cloneReferenceInfoWithoutAliases } from '@blocksuite/affine-shared/utils';
import { Bound } from '@blocksuite/global/utils'; import { Bound } from '@blocksuite/global/utils';
@@ -15,10 +16,10 @@ export class EmbedEdgelessLinkedDocBlockComponent extends toEdgelessEmbedBlock(
override convertToEmbed = () => { override convertToEmbed = () => {
const { id, doc, caption, xywh } = this.model; const { id, doc, caption, xywh } = this.model;
// synced doc entry controlled by awareness flag // synced doc entry controlled by flag
const isSyncedDocEnabled = doc.awarenessStore.getFlag( const isSyncedDocEnabled = doc
'enable_synced_doc_block' .get(FeatureFlagService)
); .getFlag('enable_synced_doc_block');
if (!isSyncedDocEnabled) { if (!isSyncedDocEnabled) {
return; return;
} }
@@ -13,6 +13,7 @@ import {
import { import {
DocDisplayMetaProvider, DocDisplayMetaProvider,
DocModeProvider, DocModeProvider,
FeatureFlagService,
ThemeProvider, ThemeProvider,
} from '@blocksuite/affine-shared/services'; } from '@blocksuite/affine-shared/services';
import { import {
@@ -129,10 +130,10 @@ export class EmbedLinkedDocBlockComponent extends EmbedBlockComponent<EmbedLinke
const { doc, caption } = this.model; const { doc, caption } = this.model;
// synced doc entry controlled by awareness flag // synced doc entry controlled by flag
const isSyncedDocEnabled = doc.awarenessStore.getFlag( const isSyncedDocEnabled = doc
'enable_synced_doc_block' .get(FeatureFlagService)
); .getFlag('enable_synced_doc_block');
if (!isSyncedDocEnabled) { if (!isSyncedDocEnabled) {
return; return;
} }
@@ -4,6 +4,7 @@ import type {
TextAlign, TextAlign,
TextVerticalAlign, TextVerticalAlign,
} from '@blocksuite/affine-model'; } from '@blocksuite/affine-model';
import { FeatureFlagService } from '@blocksuite/affine-shared/services';
import type { Bound, SerializedXYWH } from '@blocksuite/global/utils'; import type { Bound, SerializedXYWH } from '@blocksuite/global/utils';
import { deltaInsertsToChunks } from '@blocksuite/inline'; import { deltaInsertsToChunks } from '@blocksuite/inline';
@@ -68,9 +69,9 @@ export function drawGeneralShape(
const { blur, offsetX, offsetY, color } = shapeModel.shadow; const { blur, offsetX, offsetY, color } = shapeModel.shadow;
const scale = ctx.getTransform().a; const scale = ctx.getTransform().a;
const enableShadowBlur = shapeModel.surface.doc.awarenessStore.getFlag( const enableShadowBlur = shapeModel.surface.doc
'enable_shape_shadow_blur' .get(FeatureFlagService)
); .getFlag('enable_shape_shadow_blur');
// hard shadow, or soft shadow if `enable_shape_shadow_blur` is true // hard shadow, or soft shadow if `enable_shape_shadow_blur` is true
// see comment of `shape.shadow` in `ShapeElementModel` // see comment of `shape.shadow` in `ShapeElementModel`
@@ -1,5 +1,6 @@
import type { ReferenceInfo } from '@blocksuite/affine-model'; import type { ReferenceInfo } from '@blocksuite/affine-model';
import { import {
FeatureFlagService,
GenerateDocUrlProvider, GenerateDocUrlProvider,
type LinkEventType, type LinkEventType,
type TelemetryEvent, type TelemetryEvent,
@@ -305,10 +306,10 @@ export class ReferencePopup extends WithDisposable(LitElement) {
} }
private _viewSelector() { private _viewSelector() {
// synced doc entry controlled by awareness flag // synced doc entry controlled by flag
const isSyncedDocEnabled = this.doc.awarenessStore.getFlag( const isSyncedDocEnabled = this.doc
'enable_synced_doc_block' .get(FeatureFlagService)
); .getFlag('enable_synced_doc_block');
const buttons = []; const buttons = [];
buttons.push({ buttons.push({
@@ -0,0 +1,59 @@
import { type Store, StoreExtension } from '@blocksuite/store';
import { type Signal, signal } from '@preact/signals-core';
export interface BlockSuiteFlags {
enable_synced_doc_block: boolean;
enable_pie_menu: boolean;
enable_database_number_formatting: boolean;
enable_database_attachment_note: boolean;
enable_database_full_width: boolean;
enable_block_query: boolean;
enable_lasso_tool: boolean;
enable_edgeless_text: boolean;
enable_ai_onboarding: boolean;
enable_ai_chat_block: boolean;
enable_color_picker: boolean;
enable_mind_map_import: boolean;
enable_advanced_block_visibility: boolean;
enable_shape_shadow_blur: boolean;
enable_mobile_keyboard_toolbar: boolean;
enable_mobile_linked_doc_menu: boolean;
}
export class FeatureFlagService extends StoreExtension {
static override key = 'feature-flag-server';
private readonly _flags: Signal<BlockSuiteFlags> = signal({
enable_synced_doc_block: true,
enable_pie_menu: false,
enable_database_number_formatting: false,
enable_database_attachment_note: false,
enable_database_full_width: false,
enable_block_query: false,
enable_lasso_tool: false,
enable_edgeless_text: true,
enable_ai_onboarding: true,
enable_ai_chat_block: true,
enable_color_picker: true,
enable_mind_map_import: true,
enable_advanced_block_visibility: false,
enable_shape_shadow_blur: false,
enable_mobile_keyboard_toolbar: false,
enable_mobile_linked_doc_menu: false,
});
setFlag(key: keyof BlockSuiteFlags, value: boolean) {
this._flags.value = {
...this._flags.value,
[key]: value,
};
}
getFlag(key: keyof BlockSuiteFlags) {
return this._flags.value[key];
}
constructor(store: Store) {
super(store);
}
}
@@ -4,6 +4,7 @@ export * from './drag-handle-config';
export * from './edit-props-store'; export * from './edit-props-store';
export * from './editor-setting-service'; export * from './editor-setting-service';
export * from './embed-option-service'; export * from './embed-option-service';
export * from './feature-flag-service';
export * from './font-loader'; export * from './font-loader';
export * from './generate-url-service'; export * from './generate-url-service';
export * from './native-clipboard-service'; export * from './native-clipboard-service';
@@ -1,3 +1,4 @@
import { FeatureFlagService } from '@blocksuite/affine-shared/services';
import { Job, type JobMiddleware, Schema } from '@blocksuite/store'; import { Job, type JobMiddleware, Schema } from '@blocksuite/store';
import { TestWorkspace } from '@blocksuite/store/test'; import { TestWorkspace } from '@blocksuite/store/test';
@@ -22,6 +23,7 @@ export function createJob(middlewares?: JobMiddleware[]) {
testMiddlewares.push(defaultImageProxyMiddleware); testMiddlewares.push(defaultImageProxyMiddleware);
const schema = new Schema().register(AffineSchemas); const schema = new Schema().register(AffineSchemas);
const docCollection = new TestWorkspace({ schema }); const docCollection = new TestWorkspace({ schema });
docCollection.storeExtensions = [FeatureFlagService];
docCollection.meta.initialize(); docCollection.meta.initialize();
return new Job({ return new Job({
schema, schema,
+3
View File
@@ -30,6 +30,7 @@ import {
import { import {
DocDisplayMetaService, DocDisplayMetaService,
EditPropsStore, EditPropsStore,
FeatureFlagService,
FontLoaderService, FontLoaderService,
} from '@blocksuite/affine-shared/services'; } from '@blocksuite/affine-shared/services';
import type { ExtensionType } from '@blocksuite/store'; import type { ExtensionType } from '@blocksuite/store';
@@ -73,3 +74,5 @@ export const EdgelessFirstPartyBlockSpecs: ExtensionType[] = [
EdgelessTextBlockSpec, EdgelessTextBlockSpec,
FontLoaderService, FontLoaderService,
].flat(); ].flat();
export const StoreExtensions: ExtensionType[] = [FeatureFlagService];
+1
View File
@@ -1,3 +1,4 @@
export * from './common.js';
export * from './preset/edgeless-specs.js'; export * from './preset/edgeless-specs.js';
export * from './preset/mobile-patch.js'; export * from './preset/mobile-patch.js';
export * from './preset/page-specs.js'; export * from './preset/page-specs.js';
@@ -3,6 +3,7 @@ import {
type ReferenceNodeConfig, type ReferenceNodeConfig,
ReferenceNodeConfigIdentifier, ReferenceNodeConfigIdentifier,
} from '@blocksuite/affine-components/rich-text'; } from '@blocksuite/affine-components/rich-text';
import { FeatureFlagService } from '@blocksuite/affine-shared/services';
import { import {
type BlockStdScope, type BlockStdScope,
ConfigIdentifier, ConfigIdentifier,
@@ -21,9 +22,10 @@ export class MobileSpecsPatches extends LifeCycleWatcher {
constructor(std: BlockStdScope) { constructor(std: BlockStdScope) {
super(std); super(std);
const featureFlagService = std.get(FeatureFlagService);
std.doc.awarenessStore.setFlag('enable_mobile_keyboard_toolbar', true); featureFlagService.setFlag('enable_mobile_keyboard_toolbar', true);
std.doc.awarenessStore.setFlag('enable_mobile_linked_doc_menu', true); featureFlagService.setFlag('enable_mobile_linked_doc_menu', true);
} }
static override setup(di: Container) { static override setup(di: Container) {
@@ -1,6 +1,6 @@
import { SpecProvider } from '@blocksuite/affine-shared/utils'; import { SpecProvider } from '@blocksuite/affine-shared/utils';
import { CommonBlockSpecs } from './common.js'; import { CommonBlockSpecs, StoreExtensions } from './common.js';
import { EdgelessEditorBlockSpecs } from './preset/edgeless-specs.js'; import { EdgelessEditorBlockSpecs } from './preset/edgeless-specs.js';
import { PageEditorBlockSpecs } from './preset/page-specs.js'; import { PageEditorBlockSpecs } from './preset/page-specs.js';
import { import {
@@ -9,6 +9,7 @@ import {
} from './preset/preview-specs.js'; } from './preset/preview-specs.js';
export function registerSpecs() { export function registerSpecs() {
SpecProvider.getInstance().addSpec('store', StoreExtensions);
SpecProvider.getInstance().addSpec('common', CommonBlockSpecs); SpecProvider.getInstance().addSpec('common', CommonBlockSpecs);
SpecProvider.getInstance().addSpec('page', PageEditorBlockSpecs); SpecProvider.getInstance().addSpec('page', PageEditorBlockSpecs);
SpecProvider.getInstance().addSpec('edgeless', EdgelessEditorBlockSpecs); SpecProvider.getInstance().addSpec('edgeless', EdgelessEditorBlockSpecs);
@@ -26,6 +26,7 @@ import {
} from '@blocksuite/affine-model'; } from '@blocksuite/affine-model';
import { import {
EditPropsStore, EditPropsStore,
FeatureFlagService,
ThemeProvider, ThemeProvider,
} from '@blocksuite/affine-shared/services'; } from '@blocksuite/affine-shared/services';
import { captureEventTarget } from '@blocksuite/affine-shared/utils'; import { captureEventTarget } from '@blocksuite/affine-shared/utils';
@@ -249,9 +250,9 @@ export class EdgelessAutoCompletePanel extends WithDisposable(LitElement) {
const { xywh, position } = target; const { xywh, position } = target;
const bound = Bound.fromXYWH(xywh); const bound = Bound.fromXYWH(xywh);
const textFlag = this.edgeless.doc.awarenessStore.getFlag( const textFlag = this.edgeless.doc
'enable_edgeless_text' .get(FeatureFlagService)
); .getFlag('enable_edgeless_text');
if (textFlag) { if (textFlag) {
const { textId } = this.edgeless.std.command.exec('insertEdgelessText', { const { textId } = this.edgeless.std.command.exec('insertEdgelessText', {
x: bound.x, x: bound.x,
@@ -1,6 +1,7 @@
import { DefaultTheme } from '@blocksuite/affine-model'; import { DefaultTheme } from '@blocksuite/affine-model';
import { import {
EditPropsStore, EditPropsStore,
FeatureFlagService,
ThemeProvider, ThemeProvider,
} from '@blocksuite/affine-shared/services'; } from '@blocksuite/affine-shared/services';
import type { ColorEvent } from '@blocksuite/affine-shared/utils'; import type { ColorEvent } from '@blocksuite/affine-shared/utils';
@@ -65,9 +66,9 @@ export class EdgelessBrushMenu extends EdgelessToolbarToolMixin(
.value=${this._props$.value.color} .value=${this._props$.value.color}
.theme=${this._theme$.value} .theme=${this._theme$.value}
.palettes=${DefaultTheme.StrokeColorPalettes} .palettes=${DefaultTheme.StrokeColorPalettes}
.hasTransparent=${!this.edgeless.doc.awarenessStore.getFlag( .hasTransparent=${!this.edgeless.doc
'enable_color_picker' .get(FeatureFlagService)
)} .getFlag('enable_color_picker')}
@select=${(e: ColorEvent) => @select=${(e: ColorEvent) =>
this.onChange({ color: e.detail.value })} this.onChange({ color: e.detail.value })}
></edgeless-color-panel> ></edgeless-color-panel>
@@ -6,6 +6,7 @@ import {
import { ConnectorMode, DefaultTheme } from '@blocksuite/affine-model'; import { ConnectorMode, DefaultTheme } from '@blocksuite/affine-model';
import { import {
EditPropsStore, EditPropsStore,
FeatureFlagService,
ThemeProvider, ThemeProvider,
} from '@blocksuite/affine-shared/services'; } from '@blocksuite/affine-shared/services';
import type { ColorEvent } from '@blocksuite/affine-shared/utils'; import type { ColorEvent } from '@blocksuite/affine-shared/utils';
@@ -130,9 +131,9 @@ export class EdgelessConnectorMenu extends EdgelessToolbarToolMixin(
.value=${stroke} .value=${stroke}
.theme=${this._theme$.value} .theme=${this._theme$.value}
.palettes=${DefaultTheme.StrokeColorPalettes} .palettes=${DefaultTheme.StrokeColorPalettes}
.hasTransparent=${!this.edgeless.doc.awarenessStore.getFlag( .hasTransparent=${!this.edgeless.doc
'enable_color_picker' .get(FeatureFlagService)
)} .getFlag('enable_color_picker')}
@select=${(e: ColorEvent) => @select=${(e: ColorEvent) =>
this.onChange({ stroke: e.detail.value })} this.onChange({ stroke: e.detail.value })}
></edgeless-color-panel> ></edgeless-color-panel>
@@ -1,6 +1,9 @@
import { CanvasElementType } from '@blocksuite/affine-block-surface'; import { CanvasElementType } from '@blocksuite/affine-block-surface';
import { type MindmapStyle, TextElementModel } from '@blocksuite/affine-model'; import { type MindmapStyle, TextElementModel } from '@blocksuite/affine-model';
import { TelemetryProvider } from '@blocksuite/affine-shared/services'; import {
FeatureFlagService,
TelemetryProvider,
} from '@blocksuite/affine-shared/services';
import { assertInstanceOf, Bound } from '@blocksuite/global/utils'; import { assertInstanceOf, Bound } from '@blocksuite/global/utils';
import type { TemplateResult } from 'lit'; import type { TemplateResult } from 'lit';
import * as Y from 'yjs'; import * as Y from 'yjs';
@@ -103,7 +106,9 @@ export const textRender: DraggableTool['render'] = (
const w = 100; const w = 100;
const h = 32; const h = 32;
const flag = edgeless.doc.awarenessStore.getFlag('enable_edgeless_text'); const flag = edgeless.doc
.get(FeatureFlagService)
.getFlag('enable_edgeless_text');
let id: string; let id: string;
if (flag) { if (flag) {
const { textId } = edgeless.std.command.exec('insertEdgelessText', { const { textId } = edgeless.std.command.exec('insertEdgelessText', {
@@ -2,6 +2,7 @@ import { toast } from '@blocksuite/affine-components/toast';
import type { MindmapStyle } from '@blocksuite/affine-model'; import type { MindmapStyle } from '@blocksuite/affine-model';
import { import {
EditPropsStore, EditPropsStore,
FeatureFlagService,
TelemetryProvider, TelemetryProvider,
} from '@blocksuite/affine-shared/services'; } from '@blocksuite/affine-shared/services';
import type { BlockStdScope } from '@blocksuite/block-std'; import type { BlockStdScope } from '@blocksuite/block-std';
@@ -316,7 +317,7 @@ export class EdgelessMindmapMenu extends EdgelessToolbarToolMixin(
</div> </div>
`; `;
})} })}
${this.std.doc.awarenessStore.getFlag('enable_mind_map_import') ${this.std.doc.get(FeatureFlagService).getFlag('enable_mind_map_import')
? this._importMindMapEntry() ? this._importMindMapEntry()
: nothing} : nothing}
</div> </div>
@@ -12,6 +12,7 @@ import {
} from '@blocksuite/affine-model'; } from '@blocksuite/affine-model';
import { import {
EditPropsStore, EditPropsStore,
FeatureFlagService,
ThemeProvider, ThemeProvider,
} from '@blocksuite/affine-shared/services'; } from '@blocksuite/affine-shared/services';
import type { ColorEvent } from '@blocksuite/affine-shared/utils'; import type { ColorEvent } from '@blocksuite/affine-shared/utils';
@@ -173,9 +174,9 @@ export class EdgelessShapeMenu extends SignalWatcher(
.value=${fillColor} .value=${fillColor}
.theme=${this._theme$.value} .theme=${this._theme$.value}
.palettes=${DefaultTheme.FillColorPalettes} .palettes=${DefaultTheme.FillColorPalettes}
.hasTransparent=${!this.edgeless.doc.awarenessStore.getFlag( .hasTransparent=${!this.edgeless.doc
'enable_color_picker' .get(FeatureFlagService)
)} .getFlag('enable_color_picker')}
@select=${(e: ColorEvent) => this._setFillColor(e.detail)} @select=${(e: ColorEvent) => this._setFillColor(e.detail)}
></edgeless-color-panel> ></edgeless-color-panel>
</div> </div>
@@ -12,6 +12,7 @@ import {
} from '@blocksuite/affine-model'; } from '@blocksuite/affine-model';
import { import {
EditPropsStore, EditPropsStore,
FeatureFlagService,
TelemetryProvider, TelemetryProvider,
} from '@blocksuite/affine-shared/services'; } from '@blocksuite/affine-shared/services';
import { LassoMode } from '@blocksuite/affine-shared/types'; import { LassoMode } from '@blocksuite/affine-shared/types';
@@ -72,7 +73,11 @@ export class EdgelessPageKeyboardManager extends PageKeyboardManager {
this._setEdgelessTool('connector', { mode }); this._setEdgelessTool('connector', { mode });
}, },
l: () => { l: () => {
if (!rootComponent.doc.awarenessStore.getFlag('enable_lasso_tool')) { if (
!rootComponent.doc
.get(FeatureFlagService)
.getFlag('enable_lasso_tool')
) {
return; return;
} }
@@ -81,7 +86,11 @@ export class EdgelessPageKeyboardManager extends PageKeyboardManager {
}); });
}, },
'Shift-l': () => { 'Shift-l': () => {
if (!rootComponent.doc.awarenessStore.getFlag('enable_lasso_tool')) { if (
!rootComponent.doc
.get(FeatureFlagService)
.getFlag('enable_lasso_tool')
) {
return; return;
} }
// toggle between lasso modes // toggle between lasso modes
@@ -15,7 +15,10 @@ import {
ShapeElementModel, ShapeElementModel,
TextElementModel, TextElementModel,
} from '@blocksuite/affine-model'; } from '@blocksuite/affine-model';
import { TelemetryProvider } from '@blocksuite/affine-shared/services'; import {
FeatureFlagService,
TelemetryProvider,
} from '@blocksuite/affine-shared/services';
import { import {
clamp, clamp,
handleNativeRangeAtPoint, handleNativeRangeAtPoint,
@@ -783,7 +786,9 @@ export class DefaultTool extends BaseTool {
} }
if (!selected) { if (!selected) {
const textFlag = this.doc.awarenessStore.getFlag('enable_edgeless_text'); const textFlag = this.doc
.get(FeatureFlagService)
.getFlag('enable_edgeless_text');
if (textFlag) { if (textFlag) {
const [x, y] = this.gfx.viewport.toModelCoord(e.x, e.y); const [x, y] = this.gfx.viewport.toModelCoord(e.x, e.y);
@@ -1,5 +1,8 @@
import type { TextElementModel } from '@blocksuite/affine-model'; import type { TextElementModel } from '@blocksuite/affine-model';
import { TelemetryProvider } from '@blocksuite/affine-shared/services'; import {
FeatureFlagService,
TelemetryProvider,
} from '@blocksuite/affine-shared/services';
import type { PointerEventState } from '@blocksuite/block-std'; import type { PointerEventState } from '@blocksuite/block-std';
import { BaseTool, type GfxController } from '@blocksuite/block-std/gfx'; import { BaseTool, type GfxController } from '@blocksuite/block-std/gfx';
import { Bound } from '@blocksuite/global/utils'; import { Bound } from '@blocksuite/global/utils';
@@ -38,9 +41,9 @@ export class TextTool extends BaseTool {
static override toolName: string = 'text'; static override toolName: string = 'text';
override click(e: PointerEventState): void { override click(e: PointerEventState): void {
const textFlag = this.gfx.doc.awarenessStore.getFlag( const textFlag = this.gfx.doc
'enable_edgeless_text' .get(FeatureFlagService)
); .getFlag('enable_edgeless_text');
if (textFlag) { if (textFlag) {
const [x, y] = this.gfx.viewport.toModelCoord(e.x, e.y); const [x, y] = this.gfx.viewport.toModelCoord(e.x, e.y);
@@ -12,6 +12,7 @@ import {
type RootBlockModel, type RootBlockModel,
type SurfaceRefBlockModel, type SurfaceRefBlockModel,
} from '@blocksuite/affine-model'; } from '@blocksuite/affine-model';
import { FeatureFlagService } from '@blocksuite/affine-shared/services';
import { import {
matchFlavours, matchFlavours,
stopPropagation, stopPropagation,
@@ -555,9 +556,9 @@ export class EdgelessAutoConnectWidget extends WidgetComponent<
} }
override render() { override render() {
const advancedVisibilityEnabled = this.doc.awarenessStore.getFlag( const advancedVisibilityEnabled = this.doc
'enable_advanced_block_visibility' .get(FeatureFlagService)
); .getFlag('enable_advanced_block_visibility');
if (!this._show || this._dragging || !advancedVisibilityEnabled) { if (!this._show || this._dragging || !advancedVisibilityEnabled) {
return nothing; return nothing;
@@ -17,6 +17,7 @@ import {
LineWidth, LineWidth,
resolveColor, resolveColor,
} from '@blocksuite/affine-model'; } from '@blocksuite/affine-model';
import { FeatureFlagService } from '@blocksuite/affine-shared/services';
import type { ColorEvent } from '@blocksuite/affine-shared/utils'; import type { ColorEvent } from '@blocksuite/affine-shared/utils';
import { countBy, maxBy, WithDisposable } from '@blocksuite/global/utils'; import { countBy, maxBy, WithDisposable } from '@blocksuite/global/utils';
import { html, LitElement, nothing } from 'lit'; import { html, LitElement, nothing } from 'lit';
@@ -120,7 +121,9 @@ export class EdgelessChangeBrushButton extends WithDisposable(LitElement) {
<editor-toolbar-separator></editor-toolbar-separator> <editor-toolbar-separator></editor-toolbar-separator>
${when( ${when(
this.edgeless.doc.awarenessStore.getFlag('enable_color_picker'), this.edgeless.doc
.get(FeatureFlagService)
.getFlag('enable_color_picker'),
() => { () => {
const { type, colors } = packColorsWithColorScheme( const { type, colors } = packColorsWithColorScheme(
colorScheme, colorScheme,
@@ -42,6 +42,7 @@ import {
resolveColor, resolveColor,
StrokeStyle, StrokeStyle,
} from '@blocksuite/affine-model'; } from '@blocksuite/affine-model';
import { FeatureFlagService } from '@blocksuite/affine-shared/services';
import type { ColorEvent } from '@blocksuite/affine-shared/utils'; import type { ColorEvent } from '@blocksuite/affine-shared/utils';
import { countBy, maxBy, WithDisposable } from '@blocksuite/global/utils'; import { countBy, maxBy, WithDisposable } from '@blocksuite/global/utils';
import { html, LitElement, nothing, type TemplateResult } from 'lit'; import { html, LitElement, nothing, type TemplateResult } from 'lit';
@@ -361,7 +362,9 @@ export class EdgelessChangeConnectorButton extends WithDisposable(LitElement) {
return join( return join(
[ [
when( when(
this.edgeless.doc.awarenessStore.getFlag('enable_color_picker'), this.edgeless.doc
.get(FeatureFlagService)
.getFlag('enable_color_picker'),
() => { () => {
const { type, colors } = packColorsWithColorScheme( const { type, colors } = packColorsWithColorScheme(
colorScheme, colorScheme,
@@ -40,6 +40,7 @@ import {
import { import {
EmbedOptionProvider, EmbedOptionProvider,
type EmbedOptions, type EmbedOptions,
FeatureFlagService,
GenerateDocUrlProvider, GenerateDocUrlProvider,
type GenerateDocUrlService, type GenerateDocUrlService,
type LinkEventType, type LinkEventType,
@@ -390,9 +391,9 @@ export class EdgelessChangeEmbedCardButton extends WithDisposable(LitElement) {
// synced doc entry controlled by awareness flag // synced doc entry controlled by awareness flag
if (!!block && isEmbedLinkedDocBlock(block.model)) { if (!!block && isEmbedLinkedDocBlock(block.model)) {
const isSyncedDocEnabled = block.doc.awarenessStore.getFlag( const isSyncedDocEnabled = block.doc
'enable_synced_doc_block' .get(FeatureFlagService)
); .getFlag('enable_synced_doc_block');
if (!isSyncedDocEnabled) { if (!isSyncedDocEnabled) {
return false; return false;
} }
@@ -22,6 +22,7 @@ import {
NoteDisplayMode, NoteDisplayMode,
resolveColor, resolveColor,
} from '@blocksuite/affine-model'; } from '@blocksuite/affine-model';
import { FeatureFlagService } from '@blocksuite/affine-shared/services';
import type { ColorEvent } from '@blocksuite/affine-shared/utils'; import type { ColorEvent } from '@blocksuite/affine-shared/utils';
import { matchFlavours } from '@blocksuite/affine-shared/utils'; import { matchFlavours } from '@blocksuite/affine-shared/utils';
import { GfxExtensionIdentifier } from '@blocksuite/block-std/gfx'; import { GfxExtensionIdentifier } from '@blocksuite/block-std/gfx';
@@ -190,7 +191,9 @@ export class EdgelessChangeFrameButton extends WithDisposable(LitElement) {
`, `,
when( when(
this.edgeless.doc.awarenessStore.getFlag('enable_color_picker'), this.edgeless.doc
.get(FeatureFlagService)
.getFlag('enable_color_picker'),
() => { () => {
const { type, colors } = packColorsWithColorScheme( const { type, colors } = packColorsWithColorScheme(
colorScheme, colorScheme,
@@ -28,7 +28,10 @@ import {
resolveColor, resolveColor,
type StrokeStyle, type StrokeStyle,
} from '@blocksuite/affine-model'; } from '@blocksuite/affine-model';
import { ThemeProvider } from '@blocksuite/affine-shared/services'; import {
FeatureFlagService,
ThemeProvider,
} from '@blocksuite/affine-shared/services';
import { matchFlavours } from '@blocksuite/affine-shared/utils'; import { matchFlavours } from '@blocksuite/affine-shared/utils';
import { import {
assertExists, assertExists,
@@ -133,7 +136,9 @@ export class EdgelessChangeNoteButton extends WithDisposable(LitElement) {
}; };
private get _advancedVisibilityEnabled() { private get _advancedVisibilityEnabled() {
return this.doc.awarenessStore.getFlag('enable_advanced_block_visibility'); return this.doc
.get(FeatureFlagService)
.getFlag('enable_advanced_block_visibility');
} }
private get doc() { private get doc() {
@@ -301,7 +306,9 @@ export class EdgelessChangeNoteButton extends WithDisposable(LitElement) {
isDocOnly isDocOnly
? nothing ? nothing
: when( : when(
this.edgeless.doc.awarenessStore.getFlag('enable_color_picker'), this.edgeless.doc
.get(FeatureFlagService)
.getFlag('enable_color_picker'),
() => { () => {
const { type, colors } = packColorsWithColorScheme( const { type, colors } = packColorsWithColorScheme(
colorScheme, colorScheme,
@@ -34,6 +34,7 @@ import {
ShapeStyle, ShapeStyle,
StrokeStyle, StrokeStyle,
} from '@blocksuite/affine-model'; } from '@blocksuite/affine-model';
import { FeatureFlagService } from '@blocksuite/affine-shared/services';
import type { ColorEvent } from '@blocksuite/affine-shared/utils'; import type { ColorEvent } from '@blocksuite/affine-shared/utils';
import { countBy, maxBy, WithDisposable } from '@blocksuite/global/utils'; import { countBy, maxBy, WithDisposable } from '@blocksuite/global/utils';
import { css, html, LitElement, nothing, type TemplateResult } from 'lit'; import { css, html, LitElement, nothing, type TemplateResult } from 'lit';
@@ -317,7 +318,9 @@ export class EdgelessChangeShapeButton extends WithDisposable(LitElement) {
`, `,
when( when(
this.edgeless.doc.awarenessStore.getFlag('enable_color_picker'), this.edgeless.doc
.get(FeatureFlagService)
.getFlag('enable_color_picker'),
() => { () => {
const { type, colors } = packColorsWithColorScheme( const { type, colors } = packColorsWithColorScheme(
colorScheme, colorScheme,
@@ -367,7 +370,9 @@ export class EdgelessChangeShapeButton extends WithDisposable(LitElement) {
), ),
when( when(
this.edgeless.doc.awarenessStore.getFlag('enable_color_picker'), this.edgeless.doc
.get(FeatureFlagService)
.getFlag('enable_color_picker'),
() => { () => {
const { type, colors } = packColorsWithColorScheme( const { type, colors } = packColorsWithColorScheme(
colorScheme, colorScheme,
@@ -33,6 +33,7 @@ import {
TextElementModel, TextElementModel,
type TextStyleProps, type TextStyleProps,
} from '@blocksuite/affine-model'; } from '@blocksuite/affine-model';
import { FeatureFlagService } from '@blocksuite/affine-shared/services';
import type { ColorEvent } from '@blocksuite/affine-shared/utils'; import type { ColorEvent } from '@blocksuite/affine-shared/utils';
import { import {
Bound, Bound,
@@ -372,7 +373,9 @@ export class EdgelessChangeTextMenu extends WithDisposable(LitElement) {
`, `,
when( when(
this.edgeless.doc.awarenessStore.getFlag('enable_color_picker'), this.edgeless.doc
.get(FeatureFlagService)
.getFlag('enable_color_picker'),
() => { () => {
const { type, colors } = packColorsWithColorScheme( const { type, colors } = packColorsWithColorScheme(
colorScheme, colorScheme,
@@ -44,6 +44,7 @@ import {
import { import {
EmbedOptionProvider, EmbedOptionProvider,
type EmbedOptions, type EmbedOptions,
FeatureFlagService,
GenerateDocUrlProvider, GenerateDocUrlProvider,
type GenerateDocUrlService, type GenerateDocUrlService,
type LinkEventType, type LinkEventType,
@@ -203,9 +204,9 @@ export class EmbedCardToolbar extends WidgetComponent<
private get _canConvertToEmbedView() { private get _canConvertToEmbedView() {
// synced doc entry controlled by awareness flag // synced doc entry controlled by awareness flag
if (this.focusModel && isEmbedLinkedDocBlock(this.focusModel)) { if (this.focusModel && isEmbedLinkedDocBlock(this.focusModel)) {
const isSyncedDocEnabled = this.doc.awarenessStore.getFlag( const isSyncedDocEnabled = this.doc
'enable_synced_doc_block' .get(FeatureFlagService)
); .getFlag('enable_synced_doc_block');
if (!isSyncedDocEnabled) { if (!isSyncedDocEnabled) {
return false; return false;
} }
@@ -1,4 +1,5 @@
import type { RootBlockModel } from '@blocksuite/affine-model'; import type { RootBlockModel } from '@blocksuite/affine-model';
import { FeatureFlagService } from '@blocksuite/affine-shared/services';
import { WidgetComponent } from '@blocksuite/block-std'; import { WidgetComponent } from '@blocksuite/block-std';
import { IS_MOBILE } from '@blocksuite/global/env'; import { IS_MOBILE } from '@blocksuite/global/env';
import { assertType } from '@blocksuite/global/utils'; import { assertType } from '@blocksuite/global/utils';
@@ -71,7 +72,9 @@ export class AffineKeyboardToolbarWidget extends WidgetComponent<
if ( if (
this.doc.readonly || this.doc.readonly ||
!IS_MOBILE || !IS_MOBILE ||
!this.doc.awarenessStore.getFlag('enable_mobile_keyboard_toolbar') !this.doc
.get(FeatureFlagService)
.getFlag('enable_mobile_keyboard_toolbar')
) )
return nothing; return nothing;
@@ -3,6 +3,7 @@ import {
getRangeRects, getRangeRects,
type SelectionRect, type SelectionRect,
} from '@blocksuite/affine-shared/commands'; } from '@blocksuite/affine-shared/commands';
import { FeatureFlagService } from '@blocksuite/affine-shared/services';
import { getViewportElement } from '@blocksuite/affine-shared/utils'; import { getViewportElement } from '@blocksuite/affine-shared/utils';
import type { BlockComponent } from '@blocksuite/block-std'; import type { BlockComponent } from '@blocksuite/block-std';
import { BLOCK_ID_ATTR, WidgetComponent } from '@blocksuite/block-std'; import { BLOCK_ID_ATTR, WidgetComponent } from '@blocksuite/block-std';
@@ -256,9 +257,9 @@ export class AffineLinkedDocWidget extends WidgetComponent<
}, },
}; };
const enableMobile = this.doc.awarenessStore.getFlag( const enableMobile = this.doc
'enable_mobile_linked_doc_menu' .get(FeatureFlagService)
); .getFlag('enable_mobile_linked_doc_menu');
this._mode$.value = enableMobile ? mode : 'desktop'; this._mode$.value = enableMobile ? mode : 'desktop';
} }
@@ -39,7 +39,10 @@ import type {
ParagraphBlockModel, ParagraphBlockModel,
} from '@blocksuite/affine-model'; } from '@blocksuite/affine-model';
import { REFERENCE_NODE } from '@blocksuite/affine-shared/consts'; import { REFERENCE_NODE } from '@blocksuite/affine-shared/consts';
import { TelemetryProvider } from '@blocksuite/affine-shared/services'; import {
FeatureFlagService,
TelemetryProvider,
} from '@blocksuite/affine-shared/services';
import { import {
createDefaultDoc, createDefaultDoc,
openFileOrFiles, openFileOrFiles,
@@ -574,7 +577,7 @@ export const defaultSlashMenuConfig: SlashMenuConfig = {
showWhen: ({ model }) => showWhen: ({ model }) =>
model.doc.schema.flavourSchemaMap.has('affine:database') && model.doc.schema.flavourSchemaMap.has('affine:database') &&
!insideEdgelessText(model) && !insideEdgelessText(model) &&
!!model.doc.awarenessStore.getFlag('enable_block_query'), !!model.doc.get(FeatureFlagService).getFlag('enable_block_query'),
action: ({ model, rootComponent }) => { action: ({ model, rootComponent }) => {
const parent = rootComponent.doc.getParent(model); const parent = rootComponent.doc.getParent(model);
@@ -1,20 +1,4 @@
export interface BlockSuiteFlags { export interface BlockSuiteFlags {
enable_synced_doc_block: boolean;
enable_pie_menu: boolean;
enable_database_number_formatting: boolean;
enable_database_attachment_note: boolean;
enable_database_full_width: boolean;
enable_block_query: boolean;
enable_lasso_tool: boolean;
enable_edgeless_text: boolean;
enable_ai_onboarding: boolean;
enable_ai_chat_block: boolean;
enable_color_picker: boolean;
enable_mind_map_import: boolean;
enable_advanced_block_visibility: boolean;
enable_shape_shadow_blur: boolean;
enable_mobile_keyboard_toolbar: boolean;
enable_mobile_linked_doc_menu: boolean;
readonly: Record<string, boolean>; readonly: Record<string, boolean>;
} }
export * from './virtual-keyboard.js'; export * from './virtual-keyboard.js';
@@ -860,22 +860,6 @@ describe('getBlock', () => {
}); });
}); });
describe('flags', () => {
it('update flags', () => {
const options = createTestOptions();
const collection = new TestWorkspace(options);
collection.meta.initialize();
const awareness = collection.awarenessStore;
awareness.setFlag('enable_lasso_tool', false);
expect(awareness.getFlag('enable_lasso_tool')).toBe(false);
awareness.setFlag('enable_lasso_tool', true);
expect(awareness.getFlag('enable_lasso_tool')).toBe(true);
});
});
declare global { declare global {
namespace BlockSuite { namespace BlockSuite {
interface BlockModels { interface BlockModels {
@@ -688,4 +688,12 @@ export class Store {
); );
}); });
} }
get get() {
return this.provider.get.bind(this.provider);
}
get getOptional() {
return this.provider.getOptional.bind(this.provider);
}
} }
@@ -7,6 +7,7 @@ import type { Doc, GetBlocksOptions, Workspace } from '../model/index.js';
import type { Query } from '../model/store/query.js'; import type { Query } from '../model/store/query.js';
import { Store } from '../model/store/store.js'; import { Store } from '../model/store/store.js';
import type { AwarenessStore } from '../yjs/index.js'; import type { AwarenessStore } from '../yjs/index.js';
import type { TestWorkspace } from './test-workspace.js';
type DocOptions = { type DocOptions = {
id: string; id: string;
@@ -294,7 +295,9 @@ export class TestDoc implements Doc {
readonly, readonly,
query, query,
provider, provider,
extensions, extensions: (this.workspace as TestWorkspace).storeExtensions.concat(
extensions ?? []
),
}); });
this._docMap[readonlyKey].set(key, doc); this._docMap[readonlyKey].set(key, doc);
@@ -16,6 +16,7 @@ import merge from 'lodash.merge';
import { Awareness } from 'y-protocols/awareness.js'; import { Awareness } from 'y-protocols/awareness.js';
import * as Y from 'yjs'; import * as Y from 'yjs';
import type { ExtensionType } from '../extension/extension.js';
import type { import type {
CreateBlocksOptions, CreateBlocksOptions,
GetBlocksOptions, GetBlocksOptions,
@@ -46,22 +47,6 @@ export type DocCollectionOptions = {
}; };
const FLAGS_PRESET = { const FLAGS_PRESET = {
enable_synced_doc_block: false,
enable_pie_menu: false,
enable_database_number_formatting: false,
enable_database_attachment_note: false,
enable_database_full_width: false,
enable_block_query: false,
enable_lasso_tool: false,
enable_edgeless_text: true,
enable_ai_onboarding: false,
enable_ai_chat_block: false,
enable_color_picker: false,
enable_mind_map_import: false,
enable_advanced_block_visibility: false,
enable_shape_shadow_blur: false,
enable_mobile_keyboard_toolbar: false,
enable_mobile_linked_doc_menu: false,
readonly: {}, readonly: {},
} satisfies BlockSuiteFlags; } satisfies BlockSuiteFlags;
@@ -72,6 +57,8 @@ const FLAGS_PRESET = {
export class TestWorkspace implements Workspace { export class TestWorkspace implements Workspace {
protected readonly _schema: Schema; protected readonly _schema: Schema;
storeExtensions: ExtensionType[] = [];
readonly awarenessStore: AwarenessStore; readonly awarenessStore: AwarenessStore;
readonly awarenessSync: AwarenessEngine; readonly awarenessSync: AwarenessEngine;
+3 -1
View File
@@ -1,10 +1,12 @@
import { AffineSchemas } from '@blocksuite/blocks'; import { AffineSchemas, SpecProvider } from '@blocksuite/blocks';
import { Schema } from '@blocksuite/store'; import { Schema } from '@blocksuite/store';
import { TestWorkspace } from '@blocksuite/store/test'; import { TestWorkspace } from '@blocksuite/store/test';
export function createEmptyDoc() { export function createEmptyDoc() {
const schema = new Schema().register(AffineSchemas); const schema = new Schema().register(AffineSchemas);
const collection = new TestWorkspace({ schema }); const collection = new TestWorkspace({ schema });
collection.storeExtensions =
SpecProvider.getInstance().getSpec('store').value;
collection.meta.initialize(); collection.meta.initialize();
const doc = collection.createDoc(); const doc = collection.createDoc();
@@ -7,18 +7,14 @@ import {
} from '@blocksuite/blocks'; } from '@blocksuite/blocks';
import { viewPresets } from '@blocksuite/data-view/view-presets'; import { viewPresets } from '@blocksuite/data-view/view-presets';
import { assertExists } from '@blocksuite/global/utils'; import { assertExists } from '@blocksuite/global/utils';
import { type DocCollection, Text } from '@blocksuite/store'; import { Text, type Workspace } from '@blocksuite/store';
// eslint-disable-next-line @typescript-eslint/no-restricted-imports // eslint-disable-next-line @typescript-eslint/no-restricted-imports
import { propertyPresets } from '../../../../affine/data-view/src/property-presets'; import { propertyPresets } from '../../../../affine/data-view/src/property-presets';
import type { InitFn } from './utils.js'; import type { InitFn } from './utils.js';
export const database: InitFn = (collection: DocCollection, id: string) => { export const database: InitFn = (collection: Workspace, id: string) => {
const doc = collection.createDoc({ id }); const doc = collection.createDoc({ id });
doc.awarenessStore.setFlag('enable_database_number_formatting', true);
doc.awarenessStore.setFlag('enable_database_attachment_note', true);
doc.awarenessStore.setFlag('enable_database_full_width', true);
doc.awarenessStore.setFlag('enable_block_query', true);
doc.load(() => { doc.load(() => {
// Add root block and surface block at root level // Add root block and surface block at root level
@@ -1,4 +1,4 @@
import { AffineSchemas, TestUtils } from '@blocksuite/blocks'; import { AffineSchemas, SpecProvider, TestUtils } from '@blocksuite/blocks';
import type { BlockSuiteFlags } from '@blocksuite/global/types'; import type { BlockSuiteFlags } from '@blocksuite/global/types';
import { assertExists } from '@blocksuite/global/utils'; import { assertExists } from '@blocksuite/global/utils';
import { type BlockCollection, Job, nanoid, Schema } from '@blocksuite/store'; import { type BlockCollection, Job, nanoid, Schema } from '@blocksuite/store';
@@ -75,6 +75,8 @@ export function createStarterDocCollection() {
blobSources, blobSources,
}; };
const collection = new TestWorkspace(options); const collection = new TestWorkspace(options);
collection.storeExtensions =
SpecProvider.getInstance().getSpec('store').value;
collection.start(); collection.start();
// debug info // debug info
@@ -9,6 +9,7 @@ effects();
import { import {
CommunityCanvasTextFonts, CommunityCanvasTextFonts,
type DocMode, type DocMode,
FeatureFlagService,
FontConfigExtension, FontConfigExtension,
} from '@blocksuite/blocks'; } from '@blocksuite/blocks';
import { AffineSchemas } from '@blocksuite/blocks/schemas'; import { AffineSchemas } from '@blocksuite/blocks/schemas';
@@ -84,6 +85,7 @@ async function createEditor(collection: TestWorkspace, mode: DocMode = 'page') {
export async function setupEditor(mode: DocMode = 'page') { export async function setupEditor(mode: DocMode = 'page') {
const collection = new TestWorkspace(createCollectionOptions()); const collection = new TestWorkspace(createCollectionOptions());
collection.storeExtensions = [FeatureFlagService];
collection.meta.initialize(); collection.meta.initialize();
window.collection = collection; window.collection = collection;
@@ -108,10 +108,11 @@ test.describe('auto-complete', () => {
test('drag on right auto-complete button to add canvas text', async ({ test('drag on right auto-complete button to add canvas text', async ({
page, page,
}) => { }) => {
await enterPlaygroundRoom(page, { await enterPlaygroundRoom(page);
flags: { await page.evaluate(() => {
enable_edgeless_text: false, window.doc
}, .get(window.$blocksuite.blocks.FeatureFlagService)
.setFlag('enable_edgeless_text', false);
}); });
await initEmptyEdgelessState(page); await initEmptyEdgelessState(page);
await switchEditorMode(page); await switchEditorMode(page);
@@ -54,11 +54,7 @@ async function assertEdgelessTextModelRect(
test.describe('edgeless text block', () => { test.describe('edgeless text block', () => {
test.beforeEach(async ({ page }) => { test.beforeEach(async ({ page }) => {
await enterPlaygroundRoom(page, { await enterPlaygroundRoom(page);
flags: {
enable_edgeless_text: true,
},
});
await initEmptyEdgelessState(page); await initEmptyEdgelessState(page);
await switchEditorMode(page); await switchEditorMode(page);
}); });
@@ -546,11 +542,7 @@ test.describe('edgeless text block', () => {
test('press backspace at the start of first line when edgeless text exist', async ({ test('press backspace at the start of first line when edgeless text exist', async ({
page, page,
}, testInfo) => { }, testInfo) => {
await enterPlaygroundRoom(page, { await enterPlaygroundRoom(page);
flags: {
enable_edgeless_text: true,
},
});
await page.evaluate(() => { await page.evaluate(() => {
const { doc } = window; const { doc } = window;
const rootId = doc.addBlock('affine:page', { const rootId = doc.addBlock('affine:page', {
@@ -66,11 +66,7 @@ test.describe('pasting blocks', () => {
await expect(blocks.nth(3)).toContainText('code'); await expect(blocks.nth(3)).toContainText('code');
}); });
test('pasting a edgeless block', async ({ page }) => { test('pasting a edgeless block', async ({ page }) => {
await enterPlaygroundRoom(page, { await enterPlaygroundRoom(page);
flags: {
enable_edgeless_text: true,
},
});
await initEmptyEdgelessState(page); await initEmptyEdgelessState(page);
await switchEditorMode(page); await switchEditorMode(page);
await setEdgelessTool(page, 'default'); await setEdgelessTool(page, 'default');
@@ -641,10 +641,11 @@ test.describe('shape hit test', () => {
} }
test.beforeEach(async ({ page }) => { test.beforeEach(async ({ page }) => {
await enterPlaygroundRoom(page, { await enterPlaygroundRoom(page);
flags: { await page.evaluate(() => {
enable_edgeless_text: false, window.doc
}, .get(window.$blocksuite.blocks.FeatureFlagService)
.setFlag('enable_edgeless_text', false);
}); });
await initEmptyEdgelessState(page); await initEmptyEdgelessState(page);
await switchEditorMode(page); await switchEditorMode(page);
@@ -38,10 +38,11 @@ async function assertTextFont(page: Page, font: string) {
test.describe('edgeless canvas text', () => { test.describe('edgeless canvas text', () => {
test.beforeEach(async ({ page }) => { test.beforeEach(async ({ page }) => {
await enterPlaygroundRoom(page, { await enterPlaygroundRoom(page);
flags: { await page.evaluate(() => {
enable_edgeless_text: false, window.doc
}, .get(window.$blocksuite.blocks.FeatureFlagService)
.setFlag('enable_edgeless_text', false);
}); });
await initEmptyEdgelessState(page); await initEmptyEdgelessState(page);
await switchEditorMode(page); await switchEditorMode(page);
@@ -95,6 +95,9 @@ async function initEmptyEditor({
} }
const createEditor = () => { const createEditor = () => {
const editor = document.createElement('affine-editor-container'); const editor = document.createElement('affine-editor-container');
doc
.get(window.$blocksuite.blocks.FeatureFlagService)
.setFlag('enable_advanced_block_visibility', true);
editor.doc = doc; editor.doc = doc;
editor.autofocus = true; editor.autofocus = true;
const defaultExtensions: ExtensionType[] = [ const defaultExtensions: ExtensionType[] = [
@@ -3,6 +3,7 @@ import { ShadowlessElement } from '@blocksuite/affine/block-std';
import { import {
type AIError, type AIError,
DocModeProvider, DocModeProvider,
FeatureFlagService,
isInsidePageEditor, isInsidePageEditor,
PaymentRequiredError, PaymentRequiredError,
UnauthorizedError, UnauthorizedError,
@@ -141,7 +142,7 @@ export class ChatPanelMessages extends WithDisposable(ShadowlessElement) {
private _renderAIOnboarding() { private _renderAIOnboarding() {
return this.isLoading || return this.isLoading ||
!this.host?.doc.awarenessStore.getFlag('enable_ai_onboarding') !this.host?.doc.get(FeatureFlagService).getFlag('enable_ai_onboarding')
? nothing ? nothing
: html`<div : html`<div
style=${styleMap({ style=${styleMap({
@@ -2,6 +2,7 @@ import type { DefaultOpenProperty } from '@affine/core/components/doc-properties
import { import {
type DocMode, type DocMode,
EdgelessRootService, EdgelessRootService,
FeatureFlagService as BSFeatureFlagService,
HighlightSelection, HighlightSelection,
type ReferenceParams, type ReferenceParams,
} from '@blocksuite/affine/blocks'; } from '@blocksuite/affine/blocks';
@@ -16,6 +17,7 @@ import { defaults, isEqual, omit } from 'lodash-es';
import { skip } from 'rxjs'; import { skip } from 'rxjs';
import type { DocService } from '../../doc'; import type { DocService } from '../../doc';
import { AFFINE_FLAGS, type FeatureFlagService } from '../../feature-flag';
import { paramsParseOptions, preprocessParams } from '../../navigation/utils'; import { paramsParseOptions, preprocessParams } from '../../navigation/utils';
import type { WorkbenchView } from '../../workbench'; import type { WorkbenchView } from '../../workbench';
import type { WorkspaceService } from '../../workspace'; import type { WorkspaceService } from '../../workspace';
@@ -193,6 +195,8 @@ export class Editor extends Entity {
if (this.editorContainer$.value) { if (this.editorContainer$.value) {
throw new Error('already bound'); throw new Error('already bound');
} }
this._setupBlocksuiteEditorFlags(editorContainer);
this.editorContainer$.next(editorContainer); this.editorContainer$.next(editorContainer);
const unsubs: (() => void)[] = []; const unsubs: (() => void)[] = [];
@@ -325,9 +329,24 @@ export class Editor extends Entity {
}; };
} }
private _setupBlocksuiteEditorFlags(editorContainer: AffineEditorContainer) {
const affineFeatureFlagService = this.featureFlagService;
const bsFeatureFlagService = editorContainer.doc.get(BSFeatureFlagService);
Object.entries(AFFINE_FLAGS).forEach(([key, flag]) => {
if (flag.category === 'blocksuite') {
const value =
affineFeatureFlagService.flags[key as keyof AFFINE_FLAGS].value;
if (value !== undefined) {
bsFeatureFlagService.setFlag(flag.bsFlag, value);
}
}
});
}
constructor( constructor(
private readonly docService: DocService, private readonly docService: DocService,
private readonly workspaceService: WorkspaceService private readonly workspaceService: WorkspaceService,
private readonly featureFlagService: FeatureFlagService
) { ) {
super(); super();
} }
@@ -1,6 +1,7 @@
import { type Framework } from '@toeverything/infra'; import { type Framework } from '@toeverything/infra';
import { DocScope, DocService } from '../doc'; import { DocScope, DocService } from '../doc';
import { FeatureFlagService } from '../feature-flag';
import { WorkspaceScope, WorkspaceService } from '../workspace'; import { WorkspaceScope, WorkspaceService } from '../workspace';
import { Editor } from './entities/editor'; import { Editor } from './entities/editor';
import { EditorScope } from './scopes/editor'; import { EditorScope } from './scopes/editor';
@@ -18,7 +19,7 @@ export function configureEditorModule(framework: Framework) {
.scope(WorkspaceScope) .scope(WorkspaceScope)
.scope(DocScope) .scope(DocScope)
.service(EditorsService) .service(EditorsService)
.entity(Editor, [DocService, WorkspaceService]) .entity(Editor, [DocService, WorkspaceService, FeatureFlagService])
.scope(EditorScope) .scope(EditorScope)
.service(EditorService, [EditorScope]); .service(EditorService, [EditorScope]);
} }
@@ -2,27 +2,12 @@ import { OnEvent, Service } from '@toeverything/infra';
import { distinctUntilChanged, skip } from 'rxjs'; import { distinctUntilChanged, skip } from 'rxjs';
import { ApplicationStarted } from '../../lifecycle'; import { ApplicationStarted } from '../../lifecycle';
import type { Workspace } from '../../workspace';
import { WorkspaceInitialized } from '../../workspace/events';
import { AFFINE_FLAGS } from '../constant';
import { Flags, type FlagsExt } from '../entities/flags'; import { Flags, type FlagsExt } from '../entities/flags';
@OnEvent(WorkspaceInitialized, e => e.setupBlocksuiteEditorFlags)
@OnEvent(ApplicationStarted, e => e.setupRestartListener) @OnEvent(ApplicationStarted, e => e.setupRestartListener)
export class FeatureFlagService extends Service { export class FeatureFlagService extends Service {
flags = this.framework.createEntity(Flags) as FlagsExt; flags = this.framework.createEntity(Flags) as FlagsExt;
setupBlocksuiteEditorFlags(workspace: Workspace) {
for (const [key, flag] of Object.entries(AFFINE_FLAGS)) {
if (flag.category === 'blocksuite') {
const value = this.flags[key as keyof AFFINE_FLAGS].value;
if (value !== undefined) {
workspace.docCollection.awarenessStore.setFlag(flag.bsFlag, value);
}
}
}
}
setupRestartListener() { setupRestartListener() {
this.flags.enable_ai.$.pipe(distinctUntilChanged(), skip(1)).subscribe( this.flags.enable_ai.$.pipe(distinctUntilChanged(), skip(1)).subscribe(
() => { () => {
@@ -1,4 +1,4 @@
import type { BlockSuiteFlags } from '@blocksuite/affine/global/types'; import type { BlockSuiteFlags } from '@blocksuite/affine/blocks';
type FeedbackType = 'discord' | 'email' | 'github'; type FeedbackType = 'discord' | 'email' | 'github';
@@ -1,3 +1,4 @@
import { SpecProvider } from '@blocksuite/affine/blocks';
import { type Disposable, Slot } from '@blocksuite/affine/global/utils'; import { type Disposable, Slot } from '@blocksuite/affine/global/utils';
import { import {
type AwarenessStore, type AwarenessStore,
@@ -283,13 +284,18 @@ export class DocImpl implements Doc {
return this._docMap[readonlyKey].get(key) as Store; return this._docMap[readonlyKey].get(key) as Store;
} }
const storeExtensions = SpecProvider.getInstance().getSpec('store');
const extensionSet = new Set(
storeExtensions.value.concat(extensions ?? [])
);
const doc = new Store({ const doc = new Store({
doc: this, doc: this,
schema: this.workspace.schema, schema: this.workspace.schema,
readonly, readonly,
query, query,
provider, provider,
extensions, extensions: Array.from(extensionSet),
}); });
this._docMap[readonlyKey].set(key, doc); this._docMap[readonlyKey].set(key, doc);
@@ -34,22 +34,6 @@ type WorkspaceOptions = {
}; };
const FLAGS_PRESET = { const FLAGS_PRESET = {
enable_synced_doc_block: false,
enable_pie_menu: false,
enable_database_number_formatting: false,
enable_database_attachment_note: false,
enable_database_full_width: false,
enable_block_query: false,
enable_lasso_tool: false,
enable_edgeless_text: true,
enable_ai_onboarding: false,
enable_ai_chat_block: false,
enable_color_picker: false,
enable_mind_map_import: false,
enable_advanced_block_visibility: false,
enable_shape_shadow_blur: false,
enable_mobile_keyboard_toolbar: false,
enable_mobile_linked_doc_menu: false,
readonly: {}, readonly: {},
} satisfies BlockSuiteFlags; } satisfies BlockSuiteFlags;