Compare commits

..

1 Commits

Author SHA1 Message Date
liuyi ba78a6bd45 refactor(server): use duration helper 2025-06-27 11:12:15 +08:00
461 changed files with 2406 additions and 17546 deletions
-15
View File
@@ -540,11 +540,6 @@
"description": "Where the server get deployed(FQDN).\n@default \"localhost\"\n@environment `AFFINE_SERVER_HOST`",
"default": "localhost"
},
"hosts": {
"type": "array",
"description": "Multiple hosts the server will accept requests from.\n@default []",
"default": []
},
"port": {
"type": "number",
"description": "Which port the server will listen on.\n@default 3010\n@environment `AFFINE_SERVER_PORT`",
@@ -565,11 +560,6 @@
"type": "boolean",
"description": "Only allow users with early access features to access the app\n@default false",
"default": false
},
"allowGuestDemoWorkspace": {
"type": "boolean",
"description": "Whether allow guest users to create demo workspaces.\n@default true",
"default": true
}
}
},
@@ -737,11 +727,6 @@
},
"default": {}
},
"providers.morph": {
"type": "object",
"description": "The config for the morph provider.\n@default {}",
"default": {}
},
"unsplash": {
"type": "object",
"description": "The config for the unsplash key.\n@default {\"key\":\"\"}",
+5 -10
View File
@@ -126,10 +126,7 @@ const createHelmCommand = ({ isDryRun }) => {
? 'internal'
: 'dev';
const hosts = (DEPLOY_HOST || CANARY_DEPLOY_HOST)
.split(',')
.map(host => host.trim())
.filter(host => host);
const host = DEPLOY_HOST || CANARY_DEPLOY_HOST;
const deployCommand = [
`helm upgrade --install affine .github/helm/affine`,
`--namespace ${namespace}`,
@@ -138,9 +135,7 @@ const createHelmCommand = ({ isDryRun }) => {
`--set-string global.app.buildType="${buildType}"`,
`--set global.ingress.enabled=true`,
`--set-json global.ingress.annotations="{ \\"kubernetes.io/ingress.class\\": \\"gce\\", \\"kubernetes.io/ingress.allow-http\\": \\"true\\", \\"kubernetes.io/ingress.global-static-ip-name\\": \\"${STATIC_IP_NAME}\\" }"`,
...hosts.map(
(host, index) => `--set global.ingress.hosts[${index}]=${host}`
),
`--set-string global.ingress.host="${host}"`,
`--set-string global.version="${APP_VERSION}"`,
...redisAndPostgres,
...indexerOptions,
@@ -148,14 +143,14 @@ const createHelmCommand = ({ isDryRun }) => {
`--set-string web.image.tag="${imageTag}"`,
`--set graphql.replicaCount=${replica.graphql}`,
`--set-string graphql.image.tag="${imageTag}"`,
`--set graphql.app.host=${hosts[0]}`,
`--set graphql.app.host=${host}`,
`--set sync.replicaCount=${replica.sync}`,
`--set-string sync.image.tag="${imageTag}"`,
`--set-string renderer.image.tag="${imageTag}"`,
`--set renderer.app.host=${hosts[0]}`,
`--set renderer.app.host=${host}`,
`--set renderer.replicaCount=${replica.renderer}`,
`--set-string doc.image.tag="${imageTag}"`,
`--set doc.app.host=${hosts[0]}`,
`--set doc.app.host=${host}`,
`--set doc.replicaCount=${replica.doc}`,
...serviceAnnotations,
...resources,
+1 -1
View File
@@ -21,7 +21,7 @@ runs:
if [ "${{ github.ref_type }}" == "tag" ]; then
APP_VERSION=$(echo "${{ github.ref_name }}" | sed 's/^v//')
else
APP_VERSION=$(date '+%Y.%-m.%-d-canary.%-H%M')
APP_VERSION=$(date '+%Y.%-m.%-d-canary.%-H%-M')
fi
if [[ "$APP_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
BUILD_TYPE=stable
+6 -8
View File
@@ -36,8 +36,7 @@ spec:
{{- end }}
{{- end }}
rules:
{{- range .Values.global.ingress.hosts }}
- host: {{ . | quote }}
- host: "{{ .Values.global.ingress.host }}"
http:
paths:
- path: /socket.io
@@ -46,34 +45,33 @@ spec:
service:
name: affine-sync
port:
number: {{ $.Values.sync.service.port }}
number: {{ .Values.sync.service.port }}
- path: /graphql
pathType: Prefix
backend:
service:
name: affine-graphql
port:
number: {{ $.Values.graphql.service.port }}
number: {{ .Values.graphql.service.port }}
- path: /api
pathType: Prefix
backend:
service:
name: affine-graphql
port:
number: {{ $.Values.graphql.service.port }}
number: {{ .Values.graphql.service.port }}
- path: /workspace
pathType: Prefix
backend:
service:
name: affine-renderer
port:
number: {{ $.Values.renderer.service.port }}
number: {{ .Values.renderer.service.port }}
- path: /
pathType: Prefix
backend:
service:
name: affine-web
port:
number: {{ $.Values.web.service.port }}
{{- end }}
number: {{ .Values.web.service.port }}
{{- end }}
+1 -7
View File
@@ -4,13 +4,7 @@ global:
ingress:
enabled: false
className: ''
# hosts for ingress rules
# e.g.
# hosts:
# - affine.pro
# - www.affine.pro
hosts:
- affine.pro
host: affine.pro
tls: []
secret:
secretName: 'server-private-key'
+1 -1
View File
@@ -124,6 +124,7 @@ jobs:
package: 'affine_mobile_native'
no-build: 'true'
- name: Testflight
if: ${{ env.BUILD_TYPE != 'stable' }}
working-directory: packages/frontend/apps/ios/App
run: |
echo -n "${{ env.BUILD_PROVISION_PROFILE }}" | base64 --decode -o $PP_PATH
@@ -131,7 +132,6 @@ jobs:
cp $PP_PATH ~/Library/MobileDevice/Provisioning\ Profiles
fastlane beta
env:
BUILD_TARGET: distribution
BUILD_PROVISION_PROFILE: ${{ secrets.BUILD_PROVISION_PROFILE }}
PP_PATH: ${{ runner.temp }}/build_pp.mobileprovision
APPLE_STORE_CONNECT_API_KEY_ID: ${{ secrets.APPLE_STORE_CONNECT_API_KEY_ID }}
-3
View File
@@ -48,7 +48,6 @@
"@blocksuite/affine-gfx-template": "workspace:*",
"@blocksuite/affine-gfx-text": "workspace:*",
"@blocksuite/affine-gfx-turbo-renderer": "workspace:*",
"@blocksuite/affine-inline-comment": "workspace:*",
"@blocksuite/affine-inline-footnote": "workspace:*",
"@blocksuite/affine-inline-latex": "workspace:*",
"@blocksuite/affine-inline-link": "workspace:*",
@@ -174,7 +173,6 @@
"./inlines/footnote": "./src/inlines/footnote/index.ts",
"./inlines/footnote/view": "./src/inlines/footnote/view.ts",
"./inlines/footnote/store": "./src/inlines/footnote/store.ts",
"./inlines/comment": "./src/inlines/comment/index.ts",
"./inlines/latex": "./src/inlines/latex/index.ts",
"./inlines/latex/store": "./src/inlines/latex/store.ts",
"./inlines/latex/view": "./src/inlines/latex/view.ts",
@@ -285,7 +283,6 @@
"./sync": "./src/sync/index.ts",
"./extensions/store": "./src/extensions/store.ts",
"./extensions/view": "./src/extensions/view.ts",
"./foundation/clipboard": "./src/foundation/clipboard.ts",
"./foundation/store": "./src/foundation/store.ts",
"./foundation/view": "./src/foundation/view.ts"
},
@@ -33,7 +33,6 @@ import { PointerViewExtension } from '@blocksuite/affine-gfx-pointer/view';
import { ShapeViewExtension } from '@blocksuite/affine-gfx-shape/view';
import { TemplateViewExtension } from '@blocksuite/affine-gfx-template/view';
import { TextViewExtension } from '@blocksuite/affine-gfx-text/view';
import { InlineCommentViewExtension } from '@blocksuite/affine-inline-comment/view';
import { FootnoteViewExtension } from '@blocksuite/affine-inline-footnote/view';
import { LatexViewExtension as InlineLatexViewExtension } from '@blocksuite/affine-inline-latex/view';
import { LinkViewExtension } from '@blocksuite/affine-inline-link/view';
@@ -96,7 +95,6 @@ export function getInternalViewExtensions() {
RootViewExtension,
// Inline
InlineCommentViewExtension,
FootnoteViewExtension,
LinkViewExtension,
ReferenceViewExtension,
@@ -1 +0,0 @@
export * from '@blocksuite/affine-foundation/clipboard';
@@ -1 +0,0 @@
export * from '@blocksuite/affine-inline-comment';
-1
View File
@@ -45,7 +45,6 @@
{ "path": "../gfx/template" },
{ "path": "../gfx/text" },
{ "path": "../gfx/turbo-renderer" },
{ "path": "../inlines/comment" },
{ "path": "../inlines/footnote" },
{ "path": "../inlines/latex" },
{ "path": "../inlines/link" },
@@ -17,7 +17,6 @@ import {
AttachmentBlockStyles,
} from '@blocksuite/affine-model';
import {
BlockCommentManager,
CitationProvider,
DocModeProvider,
FileSizeLimitProvider,
@@ -93,14 +92,6 @@ export class AttachmentBlockComponent extends CaptionedBlockComponent<Attachment
return this.citationService.isCitationModel(this.model);
}
get isCommentHighlighted() {
return (
this.std
.getOptional(BlockCommentManager)
?.isBlockCommentHighlighted(this.model) ?? false
);
}
convertTo = () => {
return this.std
.get(AttachmentEmbedProvider)
@@ -508,7 +499,6 @@ export class AttachmentBlockComponent extends CaptionedBlockComponent<Attachment
class=${classMap({
'affine-attachment-container': true,
focused: this.selected$.value,
'comment-highlighted': this.isCommentHighlighted,
})}
style=${this.containerStyleMap}
>
@@ -10,7 +10,6 @@ import {
} from '@blocksuite/affine-shared/consts';
import {
ActionPlacement,
blockCommentToolbarButton,
type ToolbarAction,
type ToolbarActionGroup,
type ToolbarModuleConfig,
@@ -241,10 +240,6 @@ const builtinToolbarConfig = {
replaceAction,
downloadAction,
captionAction,
{
id: 'f.comment',
...blockCommentToolbarButton,
},
{
placement: ActionPlacement.More,
id: 'a.clipboard',
@@ -15,10 +15,6 @@ export const styles = css`
}
}
.affine-attachment-container.comment-highlighted {
outline: 2px solid ${unsafeCSSVarV2('block/comment/highlightUnderline')};
}
.affine-attachment-card {
display: flex;
gap: 12px;
@@ -8,7 +8,6 @@ import type {
} from '@blocksuite/affine-model';
import { ImageProxyService } from '@blocksuite/affine-shared/adapters';
import {
BlockCommentManager,
CitationProvider,
DocModeProvider,
LinkPreviewServiceIdentifier,
@@ -129,14 +128,6 @@ export class BookmarkBlockComponent extends CaptionedBlockComponent<BookmarkBloc
return this.std.get(ImageProxyService);
}
get isCommentHighlighted() {
return (
this.std
.getOptional(BlockCommentManager)
?.isBlockCommentHighlighted(this.model) ?? false
);
}
handleClick = (event: MouseEvent) => {
event.stopPropagation();
@@ -45,7 +45,6 @@ export class BookmarkCard extends SignalWatcher(
[style]: true,
selected: this.bookmark.selected$.value,
edgeless: isGfxBlockComponent(this.bookmark),
'comment-highlighted': this.bookmark.isCommentHighlighted,
});
const domainName = url.match(
@@ -1,5 +1,4 @@
import {
canEmbedAsEmbedBlock,
canEmbedAsIframe,
EMBED_IFRAME_DEFAULT_HEIGHT_IN_SURFACE,
EMBED_IFRAME_DEFAULT_WIDTH_IN_SURFACE,
@@ -17,7 +16,6 @@ import {
} from '@blocksuite/affine-shared/consts';
import {
ActionPlacement,
blockCommentToolbarButton,
EmbedIframeService,
EmbedOptionProvider,
type LinkEventType,
@@ -151,10 +149,13 @@ const builtinToolbarConfig = {
if (!model) return true;
const url = model.props.url;
// check if the url can be embedded as iframe block or other embed blocks
const options = ctx.std
.get(EmbedOptionProvider)
.getEmbedBlockOptions(url);
return (
!canEmbedAsIframe(ctx.std, url) &&
!canEmbedAsEmbedBlock(ctx.std, url)
!canEmbedAsIframe(ctx.std, url) && options?.viewType !== 'embed'
);
},
run(ctx) {
@@ -168,8 +169,15 @@ const builtinToolbarConfig = {
let blockId: string | undefined;
// first try to embed as a custom embed block
if (canEmbedAsEmbedBlock(ctx.std, url)) {
// first try to embed as iframe block
if (canEmbedAsIframe(ctx.std, url)) {
const embedIframeService = ctx.std.get(EmbedIframeService);
blockId = embedIframeService.addEmbedIframeBlock(
{ url, caption, title, description },
parent.id,
index
);
} else {
const options = ctx.std
.get(EmbedOptionProvider)
.getEmbedBlockOptions(url);
@@ -194,13 +202,6 @@ const builtinToolbarConfig = {
parent,
index
);
} else if (canEmbedAsIframe(ctx.std, url)) {
const embedIframeService = ctx.std.get(EmbedIframeService);
blockId = embedIframeService.addEmbedIframeBlock(
{ url, caption, title, description },
parent.id,
index
);
}
if (!blockId) return;
@@ -289,10 +290,6 @@ const builtinToolbarConfig = {
},
} satisfies ToolbarActionGroup<ToolbarAction>,
captionAction,
{
id: 'e.comment',
...blockCommentToolbarButton,
},
{
placement: ActionPlacement.More,
id: 'a.clipboard',
@@ -382,8 +379,27 @@ const builtinSurfaceToolbarConfig = {
let newId: string | undefined;
// first try to embed as a custom embed block
if (canEmbedAsEmbedBlock(ctx.std, url)) {
// first try to embed as iframe block
if (canEmbedAsIframe(ctx.std, url)) {
const embedIframeService = ctx.std.get(EmbedIframeService);
const config = embedIframeService.getConfig(url);
if (!config) {
return;
}
const bound = Bound.deserialize(xywh);
const options = config.options;
const { widthInSurface, heightInSurface } = options ?? {};
bound.w = widthInSurface ?? EMBED_IFRAME_DEFAULT_WIDTH_IN_SURFACE;
bound.h =
heightInSurface ?? EMBED_IFRAME_DEFAULT_HEIGHT_IN_SURFACE;
newId = ctx.store.addBlock(
'affine:embed-iframe',
{ url, caption, title, description, xywh: bound.serialize() },
parent
);
} else {
const options = ctx.std
.get(EmbedOptionProvider)
.getEmbedBlockOptions(url);
@@ -413,29 +429,8 @@ const builtinSurfaceToolbarConfig = {
},
parent
);
} else if (canEmbedAsIframe(ctx.std, url)) {
const embedIframeService = ctx.std.get(EmbedIframeService);
const config = embedIframeService.getConfig(url);
if (!config) {
return;
}
const bound = Bound.deserialize(xywh);
const options = config.options;
const { widthInSurface, heightInSurface } = options ?? {};
bound.w = widthInSurface ?? EMBED_IFRAME_DEFAULT_WIDTH_IN_SURFACE;
bound.h =
heightInSurface ?? EMBED_IFRAME_DEFAULT_HEIGHT_IN_SURFACE;
newId = ctx.store.addBlock(
'affine:embed-iframe',
{ url, caption, title, description, xywh: bound.serialize() },
parent
);
}
if (!newId) return;
ctx.command.exec(reassociateConnectorsCommand, { oldId, newId });
ctx.store.deleteBlock(model);
@@ -454,10 +449,13 @@ const builtinSurfaceToolbarConfig = {
when(ctx) {
const model = ctx.getCurrentModelByType(BookmarkBlockModel);
if (!model) return false;
const { url } = model.props;
return (
canEmbedAsIframe(ctx.std, url) || canEmbedAsEmbedBlock(ctx.std, url)
);
const options = ctx.std
.get(EmbedOptionProvider)
.getEmbedBlockOptions(url);
return canEmbedAsIframe(ctx.std, url) || options?.viewType === 'embed';
},
content(ctx) {
const model = ctx.getCurrentModelByType(BookmarkBlockModel);
@@ -1,4 +1,4 @@
import { unsafeCSSVar, unsafeCSSVarV2 } from '@blocksuite/affine-shared/theme';
import { unsafeCSSVar } from '@blocksuite/affine-shared/theme';
import { baseTheme } from '@toeverything/theme';
import { css, unsafeCSS } from 'lit';
@@ -158,10 +158,6 @@ export const styles = css`
border-radius: 4px;
}
.affine-bookmark-card.comment-highlighted {
outline: 2px solid ${unsafeCSSVarV2('block/comment/highlightUnderline')};
}
.affine-bookmark-card.loading {
.affine-bookmark-content-title-text {
color: var(--affine-placeholder-color);
@@ -13,7 +13,6 @@
"@blocksuite/affine-components": "workspace:*",
"@blocksuite/affine-ext-loader": "workspace:*",
"@blocksuite/affine-gfx-turbo-renderer": "workspace:*",
"@blocksuite/affine-inline-comment": "workspace:*",
"@blocksuite/affine-inline-latex": "workspace:*",
"@blocksuite/affine-inline-link": "workspace:*",
"@blocksuite/affine-inline-preset": "workspace:*",
@@ -1,4 +1,3 @@
import { CommentInlineSpecExtension } from '@blocksuite/affine-inline-comment';
import { LatexInlineSpecExtension } from '@blocksuite/affine-inline-latex';
import { LinkInlineSpecExtension } from '@blocksuite/affine-inline-link';
import {
@@ -21,9 +20,7 @@ import { z } from 'zod';
export const CodeBlockUnitSpecExtension =
InlineSpecExtension<AffineTextAttributes>({
name: 'code-block-unit',
schema: z.object({
'code-block-uint': z.undefined(),
}),
schema: z.undefined(),
match: () => true,
renderer: ({ delta }) => {
return html`<affine-code-unit .delta=${delta}></affine-code-unit>`;
@@ -45,6 +42,5 @@ export const CodeBlockInlineManagerExtension =
LatexInlineSpecExtension.identifier,
LinkInlineSpecExtension.identifier,
CodeBlockUnitSpecExtension.identifier,
CommentInlineSpecExtension.identifier,
],
});
@@ -6,7 +6,6 @@ import {
EDGELESS_TOP_CONTENTEDITABLE_SELECTOR,
} from '@blocksuite/affine-shared/consts';
import {
BlockCommentManager,
DocModeProvider,
NotificationProvider,
} from '@blocksuite/affine-shared/services';
@@ -391,14 +390,6 @@ export class CodeBlockComponent extends CaptionedBlockComponent<CodeBlockModel>
});
}
get isCommentHighlighted() {
return (
this.std
.getOptional(BlockCommentManager)
?.isBlockCommentHighlighted(this.model) ?? false
);
}
override async getUpdateComplete() {
const result = await super.getUpdateComplete();
await this._richTextElement?.updateComplete;
@@ -422,7 +413,6 @@ export class CodeBlockComponent extends CaptionedBlockComponent<CodeBlockModel>
<div
class=${classMap({
'affine-code-block-container': true,
'highlight-comment': this.isCommentHighlighted,
mobile: IS_MOBILE,
wrap: this.model.props.wrap,
'disable-line-numbers': !showLineNumbers,
@@ -7,10 +7,9 @@ import {
WrapIcon,
} from '@blocksuite/affine-components/icons';
import type { MenuItemGroup } from '@blocksuite/affine-components/toolbar';
import { CommentProviderIdentifier } from '@blocksuite/affine-shared/services';
import { isInsidePageEditor } from '@blocksuite/affine-shared/utils';
import { noop, sleep } from '@blocksuite/global/utils';
import { CommentIcon, NumberedListIcon } from '@blocksuite/icons/lit';
import { NumberedListIcon } from '@blocksuite/icons/lit';
import { BlockSelection } from '@blocksuite/std';
import { html } from 'lit';
import { ifDefined } from 'lit/directives/if-defined.js';
@@ -114,47 +113,6 @@ export const PRIMARY_GROUPS: MenuItemGroup<CodeBlockToolbarContext>[] = [
};
},
},
{
type: 'comment',
label: 'Comment',
tooltip: 'Comment',
icon: CommentIcon({
width: '20',
height: '20',
}),
when: ({ std }) => !!std.getOptional(CommentProviderIdentifier),
generate: ({ blockComponent }) => {
return {
action: () => {
const commentProvider = blockComponent.std.getOptional(
CommentProviderIdentifier
);
if (!commentProvider) return;
commentProvider.addComment([
new BlockSelection({
blockId: blockComponent.model.id,
}),
]);
},
render: item =>
html`<editor-icon-button
class="code-toolbar-button comment"
aria-label=${ifDefined(item.label)}
.tooltip=${item.label}
.tooltipOffset=${4}
.iconSize=${'16px'}
.iconContainerPadding=${4}
@click=${(e: MouseEvent) => {
e.stopPropagation();
item.action();
}}
>
${item.icon}
</editor-icon-button>`,
};
},
},
],
},
];
@@ -1,5 +1,4 @@
import { scrollbarStyle } from '@blocksuite/affine-shared/styles';
import { unsafeCSSVarV2 } from '@blocksuite/affine-shared/theme';
import { css } from 'lit';
export const codeBlockStyles = css`
@@ -21,10 +20,6 @@ export const codeBlockStyles = css`
padding: 12px;
}
.affine-code-block-container.highlight-comment {
outline: 2px solid ${unsafeCSSVarV2('block/comment/highlightUnderline')};
}
${scrollbarStyle('.affine-code-block-container rich-text')}
.affine-code-block-container .inline-editor {
@@ -10,7 +10,6 @@
{ "path": "../../components" },
{ "path": "../../ext-loader" },
{ "path": "../../gfx/turbo-renderer" },
{ "path": "../../inlines/comment" },
{ "path": "../../inlines/latex" },
{ "path": "../../inlines/link" },
{ "path": "../../inlines/preset" },
@@ -1,4 +1,3 @@
import { unsafeCSSVarV2 } from '@blocksuite/affine-shared/theme';
import { stopPropagation } from '@blocksuite/affine-shared/utils';
import type { DataViewUILogicBase } from '@blocksuite/data-view';
import { SignalWatcher, WithDisposable } from '@blocksuite/global/lit';
@@ -73,12 +72,6 @@ export class DatabaseTitle extends SignalWatcher(
.affine-database-title [data-title-focus='true']::before {
color: var(--affine-placeholder-color);
}
.affine-database-title.comment-highlighted {
border-bottom: 2px solid
${unsafeCSSVarV2('block/comment/highlightUnderline')};
background-color: ${unsafeCSSVarV2('block/comment/highlightActive')};
}
`;
private readonly compositionEnd = () => {
@@ -141,7 +134,6 @@ export class DatabaseTitle extends SignalWatcher(
const classList = classMap({
'affine-database-title': true,
ellipsis: !this.isFocus$.value,
'comment-highlighted': this.database?.isCommentHighlighted ?? false,
});
const untitledStyle = styleMap({
height: isEmpty ? 'auto' : 0,
@@ -10,8 +10,6 @@ import { toast } from '@blocksuite/affine-components/toast';
import type { DatabaseBlockModel } from '@blocksuite/affine-model';
import { EDGELESS_TOP_CONTENTEDITABLE_SELECTOR } from '@blocksuite/affine-shared/consts';
import {
BlockCommentManager,
CommentProviderIdentifier,
DocModeProvider,
NotificationProvider,
type TelemetryEventMap,
@@ -36,12 +34,11 @@ import {
import { widgetPresets } from '@blocksuite/data-view/widget-presets';
import { Rect } from '@blocksuite/global/gfx';
import {
CommentIcon,
CopyIcon,
DeleteIcon,
MoreHorizontalIcon,
} from '@blocksuite/icons/lit';
import { type BlockComponent, BlockSelection } from '@blocksuite/std';
import { type BlockComponent } from '@blocksuite/std';
import { RANGE_SYNC_EXCLUDE_ATTR } from '@blocksuite/std/inline';
import { Slice } from '@blocksuite/store';
import { autoUpdate } from '@floating-ui/dom';
@@ -85,18 +82,6 @@ export class DatabaseBlockComponent extends CaptionedBlockComponent<DatabaseBloc
);
},
}),
menu.action({
prefix: CommentIcon(),
name: 'Comment',
hide: () => !this.std.getOptional(CommentProviderIdentifier),
select: () => {
this.std.getOptional(CommentProviderIdentifier)?.addComment([
new BlockSelection({
blockId: this.blockId,
}),
]);
},
}),
menu.action({
prefix: CopyIcon(),
name: 'Copy',
@@ -312,14 +297,6 @@ export class DatabaseBlockComponent extends CaptionedBlockComponent<DatabaseBloc
};
}
get isCommentHighlighted() {
return (
this.std
.getOptional(BlockCommentManager)
?.isBlockCommentHighlighted(this.model) ?? false
);
}
override get topContenteditableElement() {
if (this.std.get(DocModeProvider).getEditorMode() === 'edgeless') {
return this.closest<BlockComponent>(
@@ -70,7 +70,7 @@ function toggleStyle(
return [k, v];
}
})
) as AffineTextAttributes;
);
inlineEditor.formatText(inlineRange, newAttributes, {
mode: 'merge',
@@ -11,7 +11,6 @@ import {
} from '@blocksuite/affine-shared/consts';
import {
ActionPlacement,
blockCommentToolbarButton,
DocDisplayMetaProvider,
EditorSettingProvider,
type LinkEventType,
@@ -306,10 +305,6 @@ const builtinToolbarConfig = {
},
} satisfies ToolbarActionGroup<ToolbarAction>,
captionAction,
{
id: 'e.comment',
...blockCommentToolbarButton,
},
{
placement: ActionPlacement.More,
id: 'a.clipboard',
@@ -338,7 +338,6 @@ export class EmbedLinkedDocBlockComponent extends EmbedBlockComponent<EmbedLinke
'note-empty': this.isNoteContentEmpty,
'in-canvas': inCanvas,
[this._cardStyle]: true,
'comment-highlighted': this.isCommentHighlighted,
});
const theme = this.std.get(ThemeProvider).theme;
@@ -15,10 +15,6 @@ export const styles = css`
position: relative;
}
.affine-embed-linked-doc-block.comment-highlighted {
outline: 2px solid ${unsafeCSSVarV2('block/comment/highlightUnderline')};
}
.affine-embed-linked-doc-block.in-canvas {
border: 1px solid ${unsafeCSSVarV2('layer/insideBorder/border')};
background: ${unsafeCSSVarV2('layer/background/linkedDocOnEdgeless')};
@@ -16,7 +16,6 @@ import {
import { REFERENCE_NODE } from '@blocksuite/affine-shared/consts';
import {
ActionPlacement,
blockCommentToolbarButton,
EditorSettingProvider,
type LinkEventType,
type OpenDocMode,
@@ -226,10 +225,6 @@ const builtinToolbarConfig = {
openDocActionGroup,
conversionsActionGroup,
captionAction,
{
id: 'e.comment',
...blockCommentToolbarButton,
},
{
placement: ActionPlacement.More,
id: 'a.clipboard',
@@ -232,7 +232,6 @@ export class EmbedSyncedDocBlockComponent extends EmbedBlockComponent<EmbedSynce
surface: false,
selected: this.selected$.value,
'show-hover-border': true,
'comment-highlighted': this.isCommentHighlighted,
})}
@click=${this._handleClick}
style=${containerStyleMap}
@@ -57,9 +57,6 @@ export const blockStyles = css`
border-radius: 8px;
overflow: hidden;
}
.affine-embed-synced-doc-container.comment-highlighted {
outline: 2px solid ${unsafeCSSVarV2('block/comment/highlightUnderline')};
}
.affine-embed-synced-doc-container.show-hover-border:hover {
border-color: var(--affine-border-color);
}
@@ -2,20 +2,17 @@ import {
CaptionedBlockComponent,
SelectedStyle,
} from '@blocksuite/affine-components/caption';
import type { EmbedCardStyle, EmbedProps } from '@blocksuite/affine-model';
import type { EmbedCardStyle } from '@blocksuite/affine-model';
import {
EMBED_CARD_HEIGHT,
EMBED_CARD_MIN_WIDTH,
EMBED_CARD_WIDTH,
} from '@blocksuite/affine-shared/consts';
import {
BlockCommentManager,
DocModeProvider,
} from '@blocksuite/affine-shared/services';
import { unsafeCSSVarV2 } from '@blocksuite/affine-shared/theme';
import { DocModeProvider } from '@blocksuite/affine-shared/services';
import { findAncestorModel } from '@blocksuite/affine-shared/utils';
import type { BlockService } from '@blocksuite/std';
import {
type GfxCompatibleProps,
GfxViewInteractionExtension,
type ResizeConstraint,
} from '@blocksuite/std/gfx';
@@ -28,7 +25,7 @@ import { type ClassInfo, classMap } from 'lit/directives/class-map.js';
import { type StyleInfo, styleMap } from 'lit/directives/style-map.js';
export class EmbedBlockComponent<
Model extends BlockModel<EmbedProps> = BlockModel<EmbedProps>,
Model extends BlockModel<GfxCompatibleProps> = BlockModel<GfxCompatibleProps>,
Service extends BlockService = BlockService,
WidgetName extends string = string,
> extends CaptionedBlockComponent<Model, Service, WidgetName> {
@@ -62,14 +59,6 @@ export class EmbedBlockComponent<
*/
protected embedContainerStyle: StyleInfo = {};
get isCommentHighlighted() {
return (
this.std
.getOptional(BlockCommentManager)
?.isBlockCommentHighlighted(this.model) ?? false
);
}
renderEmbed = (content: () => TemplateResult) => {
if (
this._cardStyle === 'horizontal' ||
@@ -101,11 +90,6 @@ export class EmbedBlockComponent<
style=${styleMap({
height: `${this._cardHeight}px`,
width: '100%',
...(this.isCommentHighlighted
? {
border: `2px solid ${unsafeCSSVarV2('block/comment/highlightUnderline')}`,
}
: {}),
...this.embedContainerStyle,
})}
>
@@ -57,11 +57,6 @@ export const embedNoteContentStyles = css`
font-weight: 600;
}
.affine-embed-doc-content-note-blocks inline-comment {
background-color: unset !important;
border-bottom: unset !important;
}
.affine-embed-linked-doc-block.horizontal {
affine-paragraph,
affine-list {
@@ -1,5 +1,4 @@
import { EdgelessLegacySlotIdentifier } from '@blocksuite/affine-block-surface';
import type { EmbedProps } from '@blocksuite/affine-model';
import { Bound } from '@blocksuite/global/gfx';
import {
blockComponentSymbol,
@@ -8,13 +7,16 @@ import {
GfxElementSymbol,
toGfxBlockComponent,
} from '@blocksuite/std';
import type { GfxBlockElementModel } from '@blocksuite/std/gfx';
import type {
GfxBlockElementModel,
GfxCompatibleProps,
} from '@blocksuite/std/gfx';
import type { StyleInfo } from 'lit/directives/style-map.js';
import type { EmbedBlockComponent } from './embed-block-element.js';
export function toEdgelessEmbedBlock<
Model extends GfxBlockElementModel<EmbedProps>,
Model extends GfxBlockElementModel<GfxCompatibleProps>,
Service extends BlockService,
WidgetName extends string,
B extends typeof EmbedBlockComponent<Model, Service, WidgetName>,
@@ -11,8 +11,6 @@ import {
EmbedCardLightVerticalIcon,
} from '@blocksuite/affine-components/icons';
import { ColorScheme } from '@blocksuite/affine-model';
import { EmbedOptionProvider } from '@blocksuite/affine-shared/services';
import type { BlockStdScope } from '@blocksuite/std';
import type { TemplateResult } from 'lit';
type EmbedCardIcons = {
@@ -42,8 +40,3 @@ export function getEmbedCardIcons(theme: ColorScheme): EmbedCardIcons {
};
}
}
export function canEmbedAsEmbedBlock(std: BlockStdScope, url: string) {
const options = std.get(EmbedOptionProvider).getEmbedBlockOptions(url);
return options?.viewType === 'embed';
}
@@ -13,7 +13,6 @@ import {
} from '@blocksuite/affine-shared/consts';
import {
ActionPlacement,
blockCommentToolbarButton,
EmbedOptionProvider,
type LinkEventType,
type ToolbarAction,
@@ -349,10 +348,6 @@ function createBuiltinToolbarConfigForExternal(
});
},
},
{
id: 'e.comment',
...blockCommentToolbarButton,
},
{
placement: ActionPlacement.More,
id: 'a.clipboard',
@@ -1,75 +0,0 @@
import { EmbedIframeConfigExtension } from '@blocksuite/affine-shared/services';
const GENERIC_DEFAULT_WIDTH_IN_SURFACE = 800;
const GENERIC_DEFAULT_HEIGHT_IN_SURFACE = 600;
const GENERIC_DEFAULT_WIDTH_PERCENT = 100;
const GENERIC_DEFAULT_HEIGHT_IN_NOTE = 400;
/**
* AFFiNE domains that should be excluded from generic embedding
* These are based on the centralized cloud constants and known AFFiNE domains
*/
const AFFINE_DOMAINS = [
'affine.pro', // Main AFFiNE domain
'app.affine.pro', // Stable cloud domain
'insider.affine.pro', // Beta/internal cloud domain
'affine.fail', // Canary cloud domain
'toeverything.app', // Safety measure for potential future use
'apple.getaffineapp.com', // Cloud domain for Apple app
];
/**
* Validates if a URL is suitable for generic iframe embedding
* Allows HTTPS URLs but excludes AFFiNE domains
* @param url The URL to validate
* @returns Boolean indicating if the URL can be generically embedded
*/
function isValidGenericEmbedUrl(url: string): boolean {
try {
const parsedUrl = new URL(url);
// Only allow HTTPS for security
if (parsedUrl.protocol !== 'https:') {
return false;
}
// Exclude AFFiNE domains
const hostname = parsedUrl.hostname.toLowerCase();
if (
AFFINE_DOMAINS.some(
domain => hostname === domain || hostname.endsWith(`.${domain}`)
)
) {
return false;
}
return true;
} catch {
// Invalid URL
return false;
}
}
const genericConfig = {
name: 'generic',
match: (url: string) => isValidGenericEmbedUrl(url),
buildOEmbedUrl: (url: string) => {
if (!isValidGenericEmbedUrl(url)) {
return undefined;
}
return url;
},
useOEmbedUrlDirectly: true,
options: {
widthInSurface: GENERIC_DEFAULT_WIDTH_IN_SURFACE,
heightInSurface: GENERIC_DEFAULT_HEIGHT_IN_SURFACE,
widthPercent: GENERIC_DEFAULT_WIDTH_PERCENT,
heightInNote: GENERIC_DEFAULT_HEIGHT_IN_NOTE,
allowFullscreen: true,
style: 'border: none; border-radius: 8px;',
allow: 'clipboard-read; clipboard-write; picture-in-picture;',
referrerpolicy: 'no-referrer-when-downgrade',
},
};
export const GenericEmbedConfig = EmbedIframeConfigExtension(genericConfig);
@@ -1,5 +1,4 @@
import { ExcalidrawEmbedConfig } from './excalidraw';
import { GenericEmbedConfig } from './generic';
import { GoogleDocsEmbedConfig } from './google-docs';
import { GoogleDriveEmbedConfig } from './google-drive';
import { MiroEmbedConfig } from './miro';
@@ -11,5 +10,4 @@ export const EmbedIframeConfigExtensions = [
MiroEmbedConfig,
ExcalidrawEmbedConfig,
GoogleDocsEmbedConfig,
GenericEmbedConfig,
];
@@ -18,7 +18,6 @@ import type { BaseSelection } from '@blocksuite/store';
import { computed } from '@preact/signals-core';
import { css, html, type PropertyValues } from 'lit';
import { property, query } from 'lit/decorators.js';
import { classMap } from 'lit/directives/class-map.js';
import { styleMap } from 'lit/directives/style-map.js';
import { when } from 'lit/directives/when.js';
@@ -77,10 +76,6 @@ export class ImageBlockPageComponent extends SignalWatcher(
width: 100%;
height: 100%;
}
affine-page-image .comment-highlighted {
outline: 2px solid ${unsafeCSSVarV2('block/comment/highlightUnderline')};
}
`;
resizeable$ = computed(() => this.block.resizeable$.value);
@@ -369,13 +364,7 @@ export class ImageBlockPageComponent extends SignalWatcher(
const { loading, error, icon, description, needUpload } = this.state;
return html`
<div
class=${classMap({
'resizable-img': true,
'comment-highlighted': this.block.isCommentHighlighted,
})}
style=${styleMap(imageSize)}
>
<div class="resizable-img" style=${styleMap(imageSize)}>
<img
class="drag-target"
draggable="false"
@@ -1,7 +1,6 @@
import { ImageBlockModel } from '@blocksuite/affine-model';
import {
ActionPlacement,
blockCommentToolbarButton,
type ToolbarModuleConfig,
ToolbarModuleExtension,
} from '@blocksuite/affine-shared/services';
@@ -50,10 +49,6 @@ const builtinToolbarConfig = {
});
},
},
{
id: 'c.comment',
...blockCommentToolbarButton,
},
{
placement: ActionPlacement.More,
id: 'a.clipboard',
@@ -146,10 +141,6 @@ const builtinSurfaceToolbarConfig = {
});
},
},
{
id: 'c.comment',
...blockCommentToolbarButton,
},
],
when: ctx => ctx.getSurfaceModelsByType(ImageBlockModel).length === 1,
@@ -5,10 +5,7 @@ import { Peekable } from '@blocksuite/affine-components/peek';
import { ResourceController } from '@blocksuite/affine-components/resource';
import type { ImageBlockModel } from '@blocksuite/affine-model';
import { ImageSelection } from '@blocksuite/affine-shared/selection';
import {
BlockCommentManager,
ToolbarRegistryIdentifier,
} from '@blocksuite/affine-shared/services';
import { ToolbarRegistryIdentifier } from '@blocksuite/affine-shared/services';
import { formatSize } from '@blocksuite/affine-shared/utils';
import { IS_MOBILE } from '@blocksuite/global/env';
import { BrokenImageIcon, ImageIcon } from '@blocksuite/icons/lit';
@@ -68,14 +65,6 @@ export class ImageBlockComponent extends CaptionedBlockComponent<ImageBlockModel
return this.pageImage?.resizeImg;
}
get isCommentHighlighted() {
return (
this.std
.getOptional(BlockCommentManager)
?.isBlockCommentHighlighted(this.model) ?? false
);
}
private _handleClick(event: MouseEvent) {
// the peek view need handle shift + click
if (event.defaultPrevented) return;
@@ -8,7 +8,6 @@ import {
EDGELESS_TOP_CONTENTEDITABLE_SELECTOR,
} from '@blocksuite/affine-shared/consts';
import {
BlockCommentManager,
CitationProvider,
DocModeProvider,
} from '@blocksuite/affine-shared/services';
@@ -108,14 +107,6 @@ export class ParagraphBlockComponent extends CaptionedBlockComponent<ParagraphBl
);
}
get isCommentHighlighted() {
return (
this.std
.getOptional(BlockCommentManager)
?.isBlockCommentHighlighted(this.model) ?? false
);
}
override get topContenteditableElement() {
if (this.std.get(DocModeProvider).getEditorMode() === 'edgeless') {
return this.closest<BlockComponent>(
@@ -277,10 +268,7 @@ export class ParagraphBlockComponent extends CaptionedBlockComponent<ParagraphBl
}
</style>
<div
class=${classMap({
'affine-paragraph-block-container': true,
'highlight-comment': this.isCommentHighlighted,
})}
class="affine-paragraph-block-container"
data-has-collapsed-siblings="${collapsedSiblings.length > 0}"
>
<div
@@ -1,4 +1,3 @@
import { unsafeCSSVarV2 } from '@blocksuite/affine-shared/theme';
import { css } from 'lit';
export const paragraphBlockStyles = css`
@@ -16,11 +15,6 @@ export const paragraphBlockStyles = css`
position: relative;
}
.affine-paragraph-block-container.highlight-comment {
background-color: ${unsafeCSSVarV2('block/comment/highlightActive')};
outline: 2px solid ${unsafeCSSVarV2('block/comment/highlightUnderline')};
}
affine-paragraph code {
font-size: calc(var(--affine-font-base) - 3px);
padding: 0px 4px 2px;
@@ -9,7 +9,6 @@ import {
promptDocTitle,
} from '@blocksuite/affine-block-embed';
import { updateBlockType } from '@blocksuite/affine-block-note';
import type { HighlightType } from '@blocksuite/affine-components/highlight-dropdown-menu';
import { toast } from '@blocksuite/affine-components/toast';
import { EditorChevronDown } from '@blocksuite/affine-components/toolbar';
import {
@@ -20,10 +19,6 @@ import {
isFormatSupported,
textFormatConfigs,
} from '@blocksuite/affine-inline-preset';
import {
EmbedLinkedDocBlockSchema,
EmbedSyncedDocBlockSchema,
} from '@blocksuite/affine-model';
import { textConversionConfigs } from '@blocksuite/affine-rich-text';
import {
copySelectedModelsCommand,
@@ -42,10 +37,8 @@ import type {
ToolbarActionGroup,
ToolbarModuleConfig,
} from '@blocksuite/affine-shared/services';
import {
ActionPlacement,
blockCommentToolbarButton,
} from '@blocksuite/affine-shared/services';
import { ActionPlacement } from '@blocksuite/affine-shared/services';
import type { AffineTextAttributes } from '@blocksuite/affine-shared/types';
import { tableViewMeta } from '@blocksuite/data-view/view-presets';
import {
CopyIcon,
@@ -54,11 +47,7 @@ import {
DuplicateIcon,
LinkedPageIcon,
} from '@blocksuite/icons/lit';
import {
type BlockComponent,
BlockSelection,
BlockViewIdentifier,
} from '@blocksuite/std';
import { type BlockComponent, BlockSelection } from '@blocksuite/std';
import { toDraftModel } from '@blocksuite/store';
import { html } from 'lit';
import { repeat } from 'lit/directives/repeat.js';
@@ -151,7 +140,7 @@ const highlightActionGroup = {
id: 'c.highlight',
when: ({ chain }) => isFormatSupported(chain).run()[0],
content({ chain }) {
const updateHighlight = (styles: HighlightType) => {
const updateHighlight = (styles: AffineTextAttributes) => {
const payload = { styles };
chain
.try(chain => [
@@ -172,7 +161,7 @@ const highlightActionGroup = {
} as const satisfies ToolbarAction;
const turnIntoDatabase = {
id: 'e.convert-to-database',
id: 'd.convert-to-database',
tooltip: 'Create Table',
icon: DatabaseTableViewIcon(),
when({ chain }) {
@@ -219,21 +208,10 @@ const turnIntoDatabase = {
} as const satisfies ToolbarAction;
const turnIntoLinkedDoc = {
id: 'f.convert-to-linked-doc',
id: 'e.convert-to-linked-doc',
tooltip: 'Create Linked Doc',
icon: LinkedPageIcon(),
when({ chain, std }) {
const supportFlavours = [
EmbedLinkedDocBlockSchema,
EmbedSyncedDocBlockSchema,
].map(schema => schema.model.flavour);
if (
supportFlavours.some(
flavour => !std.getOptional(BlockViewIdentifier(flavour))
)
)
return false;
when({ chain }) {
const [ok, { selectedModels }] = chain
.pipe(getSelectedModelsCommand, {
types: ['block', 'text'],
@@ -295,10 +273,6 @@ export const builtinToolbarConfig = {
highlightActionGroup,
turnIntoDatabase,
turnIntoLinkedDoc,
{
id: 'g.comment',
...blockCommentToolbarButton,
},
{
placement: ActionPlacement.More,
id: 'a.clipboard',
@@ -5,7 +5,6 @@ import {
} from '@blocksuite/affine-shared/commands';
import {
ActionPlacement,
blockCommentToolbarButton,
type ToolbarModuleConfig,
} from '@blocksuite/affine-shared/services';
import { CaptionIcon, CopyIcon, DeleteIcon } from '@blocksuite/icons/lit';
@@ -62,10 +61,6 @@ export const surfaceRefToolbarModuleConfig: ToolbarModuleConfig = {
surfaceRefBlock.captionElement.show();
},
},
{
id: 'e.comment',
...blockCommentToolbarButton,
},
{
id: 'a.clipboard',
placement: ActionPlacement.More,
@@ -13,7 +13,6 @@ import {
type SurfaceRefBlockModel,
} from '@blocksuite/affine-model';
import {
BlockCommentManager,
DocModeProvider,
EditPropsStore,
type OpenDocMode,
@@ -77,10 +76,6 @@ export class SurfaceRefBlockComponent extends BlockComponent<SurfaceRefBlockMode
border-color: ${unsafeCSSVarV2('edgeless/frame/border/active')};
}
.affine-surface-ref.comment-highlighted {
outline: 2px solid ${unsafeCSSVarV2('block/comment/highlightUnderline')};
}
@media print {
.affine-surface-ref {
outline: none !important;
@@ -142,14 +137,6 @@ export class SurfaceRefBlockComponent extends BlockComponent<SurfaceRefBlockMode
return this._referencedModel;
}
get isCommentHighlighted() {
return (
this.std
.getOptional(BlockCommentManager)
?.isBlockCommentHighlighted(this.model) ?? false
);
}
private readonly _handleClick = () => {
this.selection.update(() => {
return [this.selection.create(BlockSelection, { blockId: this.blockId })];
@@ -469,7 +456,6 @@ export class SurfaceRefBlockComponent extends BlockComponent<SurfaceRefBlockMode
class=${classMap({
'affine-surface-ref': true,
focused: this.selected$.value,
'comment-highlighted': this.isCommentHighlighted,
})}
@click=${this._handleClick}
>
@@ -1,4 +1,4 @@
import type { AffineTextStyleAttributes } from '@blocksuite/affine-shared/types';
import type { AffineTextAttributes } from '@blocksuite/affine-shared/types';
import { PropTypes, requiredProperties } from '@blocksuite/std';
import { LitElement } from 'lit';
import { property } from 'lit/decorators.js';
@@ -20,10 +20,7 @@ const colors = [
'grey',
] as const;
export type HighlightType = Pick<
AffineTextStyleAttributes,
'color' | 'background'
>;
type HighlightType = 'color' | 'background';
// TODO(@fundon): these recent settings should be added to the dropdown menu
// tests/blocksutie/e2e/format-bar.spec.ts#253
@@ -36,13 +33,13 @@ export type HighlightType = Pick<
})
export class HighlightDropdownMenu extends LitElement {
@property({ attribute: false })
accessor updateHighlight!: (styles: HighlightType) => void;
accessor updateHighlight!: (styles: AffineTextAttributes) => void;
private readonly _update = (style: HighlightType) => {
private readonly _update = (value: string | null, type: HighlightType) => {
// latestHighlightColor = value;
// latestHighlightType = type;
this.updateHighlight(style);
this.updateHighlight({ [`${type}`]: value });
};
override render() {
@@ -74,7 +71,7 @@ export class HighlightDropdownMenu extends LitElement {
return html`
<editor-menu-action
data-testid="foreground-${color}"
@click=${() => this._update({ color: value })}
@click=${() => this._update(value, 'color')}
>
<affine-text-duotone-icon
style=${styleMap({
@@ -95,7 +92,7 @@ export class HighlightDropdownMenu extends LitElement {
return html`
<editor-menu-action
data-testid="background-${color}"
@click=${() => this._update({ background: value })}
@click=${() => this._update(value, 'background')}
>
<affine-text-duotone-icon
style=${styleMap({
@@ -1,14 +1,7 @@
import { NoteBlockModel } from '@blocksuite/affine-model';
import { DocModeProvider } from '@blocksuite/affine-shared/services';
import {
isInsideEdgelessEditor,
matchModels,
} from '@blocksuite/affine-shared/utils';
import { isInsideEdgelessEditor } from '@blocksuite/affine-shared/utils';
import type { Constructor } from '@blocksuite/global/utils';
import {
GfxBlockElementModel,
GfxControllerIdentifier,
} from '@blocksuite/std/gfx';
import { GfxControllerIdentifier } from '@blocksuite/std/gfx';
import type { BlockModel } from '@blocksuite/store';
import type { LitElement, TemplateResult } from 'lit';
@@ -79,20 +72,6 @@ export const Peekable =
);
if (hitTarget && hitTarget !== model) {
// Check if hitTarget is a GfxBlockElementModel (which extends BlockModel)
// and if it's a NoteBlockModel, then check if current model is inside it
if (
hitTarget instanceof GfxBlockElementModel &&
matchModels(hitTarget, [NoteBlockModel])
) {
let curModel: BlockModel | null = model;
while (curModel) {
if (curModel === hitTarget) {
return true; // Model is inside the NoteBlockModel, allow peek
}
curModel = curModel.parent;
}
}
return false;
}
@@ -32,7 +32,6 @@
},
"exports": {
".": "./src/index.ts",
"./clipboard": "./src/clipboard.ts",
"./store": "./src/store.ts",
"./view": "./src/view.ts"
},
@@ -43,7 +43,7 @@ const imageClipboardConfigs = [
});
});
export const PlainTextClipboardConfig = ClipboardAdapterConfigExtension({
const PlainTextClipboardConfig = ClipboardAdapterConfigExtension({
mimeType: 'text/plain',
adapter: MixTextAdapter,
priority: 70,
-2
View File
@@ -9,7 +9,6 @@ import {
} from '@blocksuite/affine-ext-loader';
import {
AutoClearSelectionService,
BlockCommentManager,
CitationService,
DefaultOpenDocExtension,
DNDAPIExtension,
@@ -79,7 +78,6 @@ export class FoundationViewExtension extends ViewExtensionProvider<FoundationVie
LinkPreviewCache,
LinkPreviewService,
CitationService,
BlockCommentManager,
]);
context.register(clipboardConfigs);
if (this.isEdgeless(context.scope)) {
@@ -1,46 +0,0 @@
{
"name": "@blocksuite/affine-inline-comment",
"description": "Inline comment for BlockSuite.",
"type": "module",
"scripts": {
"build": "tsc"
},
"sideEffects": false,
"keywords": [],
"author": "toeverything",
"license": "MIT",
"dependencies": {
"@blocksuite/affine-ext-loader": "workspace:*",
"@blocksuite/affine-model": "workspace:*",
"@blocksuite/affine-rich-text": "workspace:*",
"@blocksuite/affine-shared": "workspace:*",
"@blocksuite/global": "workspace:*",
"@blocksuite/std": "workspace:*",
"@blocksuite/store": "workspace:*",
"@lit/context": "^1.1.2",
"@preact/signals-core": "^1.8.0",
"@toeverything/theme": "^1.1.15",
"@types/lodash-es": "^4.17.12",
"lit": "^3.2.0",
"lit-html": "^3.2.1",
"lodash-es": "^4.17.21",
"rxjs": "^7.8.1",
"yjs": "^13.6.21",
"zod": "^3.23.8"
},
"devDependencies": {
"vitest": "3.1.3"
},
"exports": {
".": "./src/index.ts",
"./view": "./src/view.ts",
"./store": "./src/store.ts"
},
"files": [
"src",
"dist",
"!src/__tests__",
"!dist/__tests__"
],
"version": "0.21.0"
}
@@ -1,11 +0,0 @@
import { InlineComment } from './inline-comment';
export function effects() {
customElements.define('inline-comment', InlineComment);
}
declare global {
interface HTMLElementTagNameMap {
'inline-comment': InlineComment;
}
}
@@ -1,2 +0,0 @@
export * from './inline-spec';
export * from './utils';
@@ -1,175 +0,0 @@
import { getInlineEditorByModel } from '@blocksuite/affine-rich-text';
import { getSelectedBlocksCommand } from '@blocksuite/affine-shared/commands';
import {
type CommentId,
CommentProviderIdentifier,
} from '@blocksuite/affine-shared/services';
import type { AffineInlineEditor } from '@blocksuite/affine-shared/types';
import { DisposableGroup } from '@blocksuite/global/disposable';
import {
LifeCycleWatcher,
type TextRangePoint,
TextSelection,
} from '@blocksuite/std';
import type { BaseSelection, BlockModel } from '@blocksuite/store';
import { signal } from '@preact/signals-core';
import { extractCommentIdFromDelta, findCommentedTexts } from './utils';
export class InlineCommentManager extends LifeCycleWatcher {
static override key = 'inline-comment-manager';
private readonly _disposables = new DisposableGroup();
private readonly _highlightedCommentId$ = signal<CommentId | null>(null);
private get _provider() {
return this.std.getOptional(CommentProviderIdentifier);
}
override mounted() {
const provider = this._provider;
if (!provider) return;
this._disposables.add(provider.onCommentAdded(this._handleAddComment));
this._disposables.add(
provider.onCommentDeleted(this._handleDeleteAndResolve)
);
this._disposables.add(
provider.onCommentResolved(this._handleDeleteAndResolve)
);
this._disposables.add(
provider.onCommentHighlighted(this._handleHighlightComment)
);
this._disposables.add(
this.std.selection.slots.changed.subscribe(this._handleSelectionChanged)
);
}
override unmounted() {
this._disposables.dispose();
}
private readonly _handleAddComment = (
id: CommentId,
selections: BaseSelection[]
) => {
const needCommentTexts = selections
.map(selection => {
if (!selection.is(TextSelection)) return [];
const [_, { selectedBlocks }] = this.std.command
.chain()
.pipe(getSelectedBlocksCommand, {
textSelection: selection,
})
.run();
if (!selectedBlocks) return [];
type MakeRequired<T, K extends keyof T> = T & {
[key in K]: NonNullable<T[key]>;
};
return selectedBlocks
.map(
({ model }) =>
[model, getInlineEditorByModel(this.std, model)] as const
)
.filter(
(
pair
): pair is [MakeRequired<BlockModel, 'text'>, AffineInlineEditor] =>
!!pair[0].text && !!pair[1]
)
.map(([model, inlineEditor]) => {
let from: TextRangePoint;
let to: TextRangePoint | null;
if (model.id === selection.from.blockId) {
from = selection.from;
to = null;
} else if (model.id === selection.to?.blockId) {
from = selection.to;
to = null;
} else {
from = {
blockId: model.id,
index: 0,
length: model.text.yText.length,
};
to = null;
}
return [new TextSelection({ from, to }), inlineEditor] as const;
});
})
.flat();
if (needCommentTexts.length === 0) return;
needCommentTexts.forEach(([selection, inlineEditor]) => {
inlineEditor.formatText(
selection.from,
{
[`comment-${id}`]: true,
},
{
withoutTransact: true,
}
);
});
};
private readonly _handleDeleteAndResolve = (id: CommentId) => {
const commentedTexts = findCommentedTexts(this.std, id);
if (commentedTexts.length === 0) return;
this.std.store.withoutTransact(() => {
commentedTexts.forEach(([selection, inlineEditor]) => {
inlineEditor.formatText(
selection.from,
{
[`comment-${id}`]: null,
},
{
withoutTransact: true,
}
);
});
});
};
private readonly _handleHighlightComment = (id: CommentId | null) => {
this._highlightedCommentId$.value = id;
};
private readonly _handleSelectionChanged = (selections: BaseSelection[]) => {
const currentHighlightedCommentId = this._highlightedCommentId$.peek();
if (selections.length === 1) {
const selection = selections[0];
// InlineCommentManager only handle text selection
if (!selection.is(TextSelection)) return;
if (!selection.isCollapsed() && currentHighlightedCommentId !== null) {
this._provider?.highlightComment(null);
return;
}
const model = this.std.store.getModelById(selection.from.blockId);
if (!model) return;
const inlineEditor = getInlineEditorByModel(this.std, model);
if (!inlineEditor) return;
const delta = inlineEditor.getDeltaByRangeIndex(selection.from.index);
if (!delta) return;
const commentIds = extractCommentIdFromDelta(delta);
if (commentIds.length !== 0) return;
}
if (currentHighlightedCommentId !== null) {
this._provider?.highlightComment(null);
}
};
}
@@ -1,95 +0,0 @@
import {
type CommentId,
CommentProviderIdentifier,
} from '@blocksuite/affine-shared/services';
import { unsafeCSSVarV2 } from '@blocksuite/affine-shared/theme';
import { WithDisposable } from '@blocksuite/global/lit';
import {
type BlockStdScope,
PropTypes,
requiredProperties,
ShadowlessElement,
stdContext,
} from '@blocksuite/std';
import { consume } from '@lit/context';
import { css, type PropertyValues } from 'lit';
import { property, state } from 'lit/decorators.js';
import { html } from 'lit-html';
import { isEqual } from 'lodash-es';
@requiredProperties({
commentIds: PropTypes.arrayOf(id => typeof id === 'string'),
})
export class InlineComment extends WithDisposable(ShadowlessElement) {
static override styles = css`
inline-comment {
display: inline-block;
background-color: ${unsafeCSSVarV2('block/comment/highlightDefault')};
border-bottom: 2px solid
${unsafeCSSVarV2('block/comment/highlightUnderline')};
}
inline-comment.highlighted {
background-color: ${unsafeCSSVarV2('block/comment/highlightActive')};
}
`;
@property({
attribute: false,
hasChanged: (newVal: string[], oldVal: string[]) =>
!isEqual(newVal, oldVal),
})
accessor commentIds!: string[];
@consume({ context: stdContext })
private accessor _std!: BlockStdScope;
@state()
accessor highlighted = false;
private get _provider() {
return this._std.getOptional(CommentProviderIdentifier);
}
private readonly _handleClick = () => {
const provider = this._provider;
provider && this.commentIds.forEach(id => provider.highlightComment(id));
};
private readonly _handleHighlight = (id: CommentId | null) => {
if (this.highlighted) {
if (!id || !this.commentIds.includes(id)) {
this.highlighted = false;
}
} else {
if (id && this.commentIds.includes(id)) {
this.highlighted = true;
}
}
};
override connectedCallback() {
super.connectedCallback();
const provider = this._provider;
if (provider) {
this.disposables.addFromEvent(this, 'click', this._handleClick);
this.disposables.add(
provider.onCommentHighlighted(this._handleHighlight)
);
}
}
override willUpdate(_changedProperties: PropertyValues<this>) {
if (_changedProperties.has('highlighted')) {
if (this.highlighted) {
this.classList.add('highlighted');
} else {
this.classList.remove('highlighted');
}
}
}
override render() {
return html`<slot></slot>`;
}
}
@@ -1,38 +0,0 @@
import { type CommentId } from '@blocksuite/affine-shared/services';
import type { AffineTextAttributes } from '@blocksuite/affine-shared/types';
import { dynamicSchema, InlineSpecExtension } from '@blocksuite/std/inline';
import { html, nothing } from 'lit-html';
import { when } from 'lit-html/directives/when.js';
import { z } from 'zod';
import { extractCommentIdFromDelta } from './utils';
type InlineCommendId = `comment-${CommentId}`;
function isInlineCommendId(key: string): key is InlineCommendId {
return key.startsWith('comment-');
}
export const CommentInlineSpecExtension =
InlineSpecExtension<AffineTextAttributes>({
name: 'comment',
schema: dynamicSchema(
isInlineCommendId,
z.boolean().optional().nullable().catch(undefined)
),
match: delta => {
if (!delta.attributes) return false;
const comments = Object.entries(delta.attributes).filter(
([key, value]) => isInlineCommendId(key) && value === true
);
return comments.length > 0;
},
renderer: ({ delta, children }) =>
html`<inline-comment .commentIds=${extractCommentIdFromDelta(delta)}
>${when(
children,
() => html`${children}`,
() => nothing
)}</inline-comment
>`,
wrapper: true,
});
@@ -1,53 +0,0 @@
import { getInlineEditorByModel } from '@blocksuite/affine-rich-text';
import type { CommentId } from '@blocksuite/affine-shared/services';
import type { AffineTextAttributes } from '@blocksuite/affine-shared/types';
import { type BlockStdScope, TextSelection } from '@blocksuite/std';
import type { InlineEditor } from '@blocksuite/std/inline';
import type { DeltaInsert } from '@blocksuite/store';
export function findCommentedTexts(std: BlockStdScope, commentId: CommentId) {
const selections: [TextSelection, InlineEditor][] = [];
std.store.getAllModels().forEach(model => {
const inlineEditor = getInlineEditorByModel(std, model);
if (!inlineEditor) return;
inlineEditor.mapDeltasInInlineRange(
{
index: 0,
length: inlineEditor.yTextLength,
},
(delta, rangeIndex) => {
if (
delta.attributes &&
Object.keys(delta.attributes).some(
key => key === `comment-${commentId}`
)
) {
selections.push([
new TextSelection({
from: {
blockId: model.id,
index: rangeIndex,
length: delta.insert.length,
},
to: null,
}),
inlineEditor,
]);
}
}
);
});
return selections;
}
export function extractCommentIdFromDelta(
delta: DeltaInsert<AffineTextAttributes>
) {
if (!delta.attributes) return [];
return Object.keys(delta.attributes)
.filter(key => key.startsWith('comment-'))
.map(key => key.replace('comment-', ''));
}
@@ -1,22 +0,0 @@
import {
type ViewExtensionContext,
ViewExtensionProvider,
} from '@blocksuite/affine-ext-loader';
import { effects } from './effects';
import { InlineCommentManager } from './inline-comment-manager';
import { CommentInlineSpecExtension } from './inline-spec';
export class InlineCommentViewExtension extends ViewExtensionProvider {
override name = 'affine-inline-comment';
override effect(): void {
super.effect();
effects();
}
override setup(context: ViewExtensionContext) {
super.setup(context);
context.register([CommentInlineSpecExtension, InlineCommentManager]);
}
}
@@ -1,18 +0,0 @@
{
"extends": "../../../tsconfig.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./dist",
"tsBuildInfoFile": "./dist/tsconfig.tsbuildinfo"
},
"include": ["./src"],
"references": [
{ "path": "../../ext-loader" },
{ "path": "../../model" },
{ "path": "../../rich-text" },
{ "path": "../../shared" },
{ "path": "../../../framework/global" },
{ "path": "../../../framework/std" },
{ "path": "../../../framework/store" }
]
}
@@ -3,7 +3,6 @@ import type { AffineTextAttributes } from '@blocksuite/affine-shared/types';
import { StdIdentifier } from '@blocksuite/std';
import { InlineSpecExtension } from '@blocksuite/std/inline';
import { html } from 'lit';
import z from 'zod';
import { FootNoteNodeConfigIdentifier } from './footnote-node/footnote-config';
@@ -14,9 +13,7 @@ export const FootNoteInlineSpecExtension =
provider.getOptional(FootNoteNodeConfigIdentifier) ?? undefined;
return {
name: 'footnote',
schema: z.object({
footnote: FootNoteSchema.optional().nullable().catch(undefined),
}),
schema: FootNoteSchema.optional().nullable().catch(undefined),
match: delta => {
return !!delta.attributes?.footnote;
},
@@ -9,9 +9,7 @@ export const LatexInlineSpecExtension =
const std = provider.get(StdIdentifier);
return {
name: 'latex',
schema: z.object({
latex: z.string().optional().nullable().catch(undefined),
}),
schema: z.string().optional().nullable().catch(undefined),
match: delta => typeof delta.attributes?.latex === 'string',
renderer: ({ delta, selected, editor, startOffset, endOffset }) => {
return html`<affine-latex-node
@@ -30,9 +28,7 @@ export const LatexInlineSpecExtension =
export const LatexEditorUnitSpecExtension =
InlineSpecExtension<AffineTextAttributes>({
name: 'latex-editor-unit',
schema: z.object({
'latex-editor-unit': z.undefined(),
}),
schema: z.undefined(),
match: () => true,
renderer: ({ delta }) => {
return html`<latex-editor-unit .delta=${delta}></latex-editor-unit>`;
@@ -9,9 +9,7 @@ export const LinkInlineSpecExtension =
const std = provider.get(StdIdentifier);
return {
name: 'link',
schema: z.object({
link: z.string().optional().nullable().catch(undefined),
}),
schema: z.string().optional().nullable().catch(undefined),
match: delta => {
return !!delta.attributes?.link;
},
@@ -228,20 +228,23 @@ export const builtinInlineLinkToolbarConfig = {
const props = { url };
let blockId: string | undefined;
// first try to embed as iframe block
const embedIframeService = ctx.std.get(EmbedIframeService);
const embedOptions = ctx.std
.get(EmbedOptionProvider)
.getEmbedBlockOptions(url);
if (embedOptions?.viewType === 'embed') {
const flavour = embedOptions.flavour;
blockId = ctx.store.addBlock(flavour, props, parent, index + 1);
} else if (embedIframeService.canEmbed(url)) {
if (embedIframeService.canEmbed(url)) {
blockId = embedIframeService.addEmbedIframeBlock(
props,
parent.id,
index + 1
);
} else {
// if not, try to add as other embed link block
const options = ctx.std
.get(EmbedOptionProvider)
.getEmbedBlockOptions(url);
if (options?.viewType !== 'embed') return;
const flavour = options.flavour;
blockId = ctx.store.addBlock(flavour, props, parent, index + 1);
}
if (!blockId) return;
@@ -23,7 +23,7 @@ export class AffineMention extends SignalWatcher(
'clig' off;
/* Client/baseMedium */
font-family: Inter;
font-size: var(--affine-font-size-base);
font-size: 15px;
font-style: normal;
font-weight: 500;
line-height: 24px; /* 160% */
@@ -9,16 +9,14 @@ export const MentionInlineSpecExtension =
const std = provider.get(StdIdentifier);
return {
name: 'mention',
schema: z.object({
mention: z
.object({
member: z.string(),
notification: z.string().optional(),
})
.optional()
.nullable()
.catch(undefined),
}),
schema: z
.object({
member: z.string(),
notification: z.string().optional(),
})
.optional()
.nullable()
.catch(undefined),
match: delta => {
return !!delta.attributes?.mention?.member;
},
@@ -12,7 +12,6 @@
"dependencies": {
"@blocksuite/affine-components": "workspace:*",
"@blocksuite/affine-ext-loader": "workspace:*",
"@blocksuite/affine-inline-comment": "workspace:*",
"@blocksuite/affine-inline-footnote": "workspace:*",
"@blocksuite/affine-inline-latex": "workspace:*",
"@blocksuite/affine-inline-link": "workspace:*",
@@ -11,7 +11,7 @@ import { type EditorHost, TextSelection } from '@blocksuite/std';
import type { TemplateResult } from 'lit';
import {
isTextAttributeActive,
isTextStyleActive,
toggleBold,
toggleCode,
toggleItalic,
@@ -38,7 +38,7 @@ export const textFormatConfigs: TextFormatConfig[] = [
activeWhen: host => {
const [result] = host.std.command
.chain()
.pipe(isTextAttributeActive, { key: 'bold' })
.pipe(isTextStyleActive, { key: 'bold' })
.run();
return result;
},
@@ -54,7 +54,7 @@ export const textFormatConfigs: TextFormatConfig[] = [
activeWhen: host => {
const [result] = host.std.command
.chain()
.pipe(isTextAttributeActive, { key: 'italic' })
.pipe(isTextStyleActive, { key: 'italic' })
.run();
return result;
},
@@ -70,7 +70,7 @@ export const textFormatConfigs: TextFormatConfig[] = [
activeWhen: host => {
const [result] = host.std.command
.chain()
.pipe(isTextAttributeActive, { key: 'underline' })
.pipe(isTextStyleActive, { key: 'underline' })
.run();
return result;
},
@@ -86,7 +86,7 @@ export const textFormatConfigs: TextFormatConfig[] = [
activeWhen: host => {
const [result] = host.std.command
.chain()
.pipe(isTextAttributeActive, { key: 'strike' })
.pipe(isTextStyleActive, { key: 'strike' })
.run();
return result;
},
@@ -102,7 +102,7 @@ export const textFormatConfigs: TextFormatConfig[] = [
activeWhen: host => {
const [result] = host.std.command
.chain()
.pipe(isTextAttributeActive, { key: 'code' })
.pipe(isTextStyleActive, { key: 'code' })
.run();
return result;
},
@@ -118,7 +118,7 @@ export const textFormatConfigs: TextFormatConfig[] = [
activeWhen: host => {
const [result] = host.std.command
.chain()
.pipe(isTextAttributeActive, { key: 'link' })
.pipe(isTextStyleActive, { key: 'link' })
.run();
return result;
},
@@ -1,9 +1,6 @@
import { clearMarksOnDiscontinuousInput } from '@blocksuite/affine-rich-text';
import { getSelectedBlocksCommand } from '@blocksuite/affine-shared/commands';
import type {
AffineTextAttributes,
AffineTextStyleAttributes,
} from '@blocksuite/affine-shared/types';
import type { AffineTextAttributes } from '@blocksuite/affine-shared/types';
import type { Command, TextSelection } from '@blocksuite/std';
import {
INLINE_ROOT_ATTR,
@@ -16,7 +13,7 @@ import { FORMAT_TEXT_SUPPORT_FLAVOURS } from './consts.js';
export const formatTextCommand: Command<{
currentTextSelection?: TextSelection;
textSelection?: TextSelection;
styles: AffineTextStyleAttributes;
styles: AffineTextAttributes;
mode?: 'replace' | 'merge';
}> = (ctx, next) => {
const { styles, mode = 'merge' } = ctx;
@@ -10,8 +10,8 @@ export { formatBlockCommand } from './format-block.js';
export { formatNativeCommand } from './format-native.js';
export { formatTextCommand } from './format-text.js';
export {
getTextAttributes,
isTextAttributeActive,
getTextStyle,
isTextStyleActive,
toggleBold,
toggleCode,
toggleItalic,
@@ -2,31 +2,25 @@ import {
getBlockSelectionsCommand,
getTextSelectionCommand,
} from '@blocksuite/affine-shared/commands';
import type {
AffineTextAttributes,
AffineTextStyleAttributes,
} from '@blocksuite/affine-shared/types';
import type { AffineTextAttributes } from '@blocksuite/affine-shared/types';
import type { Command } from '@blocksuite/std';
import { formatBlockCommand } from './format-block.js';
import { formatNativeCommand } from './format-native.js';
import { formatTextCommand } from './format-text.js';
import { getCombinedTextAttributes } from './utils.js';
import { getCombinedTextStyle } from './utils.js';
export const toggleTextStyleCommand: Command<{
key: Extract<
keyof AffineTextStyleAttributes,
keyof AffineTextAttributes,
'bold' | 'italic' | 'underline' | 'strike' | 'code'
>;
}> = (ctx, next) => {
const { std, key } = ctx;
const [active] = std.command
.chain()
.pipe(isTextAttributeActive, { key })
.run();
const [active] = std.command.chain().pipe(isTextStyleActive, { key }).run();
const payload: {
styles: AffineTextStyleAttributes;
styles: AffineTextAttributes;
mode?: 'replace' | 'merge';
} = {
styles: {
@@ -52,7 +46,7 @@ export const toggleTextStyleCommand: Command<{
const toggleTextStyleCommandWrapper = (
key: Extract<
keyof AffineTextStyleAttributes,
keyof AffineTextAttributes,
'bold' | 'italic' | 'underline' | 'strike' | 'code'
>
): Command => {
@@ -72,29 +66,30 @@ export const toggleUnderline = toggleTextStyleCommandWrapper('underline');
export const toggleStrike = toggleTextStyleCommandWrapper('strike');
export const toggleCode = toggleTextStyleCommandWrapper('code');
export const getTextAttributes: Command<
{},
{ textAttributes: AffineTextAttributes }
> = (ctx, next) => {
const [result, innerCtx] = getCombinedTextAttributes(
export const getTextStyle: Command<{}, { textStyle: AffineTextAttributes }> = (
ctx,
next
) => {
const [result, innerCtx] = getCombinedTextStyle(
ctx.std.command.chain()
).run();
if (!result) {
return false;
}
return next({ textAttributes: innerCtx.textAttributes });
return next({ textStyle: innerCtx.textStyle });
};
export const isTextAttributeActive: Command<{
key: keyof AffineTextAttributes;
}> = (ctx, next) => {
export const isTextStyleActive: Command<{ key: keyof AffineTextAttributes }> = (
ctx,
next
) => {
const key = ctx.key;
const [result] = getCombinedTextAttributes(ctx.std.command.chain())
const [result] = getCombinedTextStyle(ctx.std.command.chain())
.pipe((ctx, next) => {
const { textAttributes } = ctx;
const { textStyle } = ctx;
if (textAttributes && key in textAttributes) {
if (textStyle && key in textStyle) {
return next();
}
@@ -77,8 +77,8 @@ function handleCurrentSelection(
handler: (
type: 'text' | 'block' | 'native',
inlineEditors: InlineEditor<AffineTextAttributes>[]
) => { textAttributes: AffineTextAttributes } | boolean | void
): Chain<InitCommandCtx & { textAttributes: AffineTextAttributes }> {
) => { textStyle: AffineTextAttributes } | boolean | void
): Chain<InitCommandCtx & { textStyle: AffineTextAttributes }> {
return chain.try(chain => [
// text selection, corresponding to `formatText` command
chain
@@ -174,25 +174,25 @@ function handleCurrentSelection(
]);
}
export function getCombinedTextAttributes(chain: Chain<InitCommandCtx>) {
export function getCombinedTextStyle(chain: Chain<InitCommandCtx>) {
return handleCurrentSelection(chain, (type, inlineEditors) => {
if (type === 'text') {
return {
textAttributes: getCombinedFormatFromInlineEditors(
textStyle: getCombinedFormatFromInlineEditors(
inlineEditors.map(e => [e, e.getInlineRange()])
),
};
}
if (type === 'block') {
return {
textAttributes: getCombinedFormatFromInlineEditors(
textStyle: getCombinedFormatFromInlineEditors(
inlineEditors.map(e => [e, { index: 0, length: e.yTextLength }])
),
};
}
if (type === 'native') {
return {
textAttributes: getCombinedFormatFromInlineEditors(
textStyle: getCombinedFormatFromInlineEditors(
inlineEditors.map(e => [e, e.getInlineRange()])
),
};
@@ -1,4 +1,3 @@
import { CommentInlineSpecExtension } from '@blocksuite/affine-inline-comment';
import { FootNoteInlineSpecExtension } from '@blocksuite/affine-inline-footnote';
import { LatexInlineSpecExtension } from '@blocksuite/affine-inline-latex';
import { LinkInlineSpecExtension } from '@blocksuite/affine-inline-link';
@@ -33,6 +32,5 @@ export const DefaultInlineManagerExtension =
LinkInlineSpecExtension.identifier,
FootNoteInlineSpecExtension.identifier,
MentionInlineSpecExtension.identifier,
CommentInlineSpecExtension.identifier,
],
});
@@ -12,9 +12,7 @@ export type AffineInlineRootElement = InlineRootElement<AffineTextAttributes>;
export const BoldInlineSpecExtension =
InlineSpecExtension<AffineTextAttributes>({
name: 'bold',
schema: z.object({
bold: z.literal(true).optional().nullable().catch(undefined),
}),
schema: z.literal(true).optional().nullable().catch(undefined),
match: delta => {
return !!delta.attributes?.bold;
},
@@ -26,9 +24,7 @@ export const BoldInlineSpecExtension =
export const ItalicInlineSpecExtension =
InlineSpecExtension<AffineTextAttributes>({
name: 'italic',
schema: z.object({
italic: z.literal(true).optional().nullable().catch(undefined),
}),
schema: z.literal(true).optional().nullable().catch(undefined),
match: delta => {
return !!delta.attributes?.italic;
},
@@ -40,9 +36,7 @@ export const ItalicInlineSpecExtension =
export const UnderlineInlineSpecExtension =
InlineSpecExtension<AffineTextAttributes>({
name: 'underline',
schema: z.object({
underline: z.literal(true).optional().nullable().catch(undefined),
}),
schema: z.literal(true).optional().nullable().catch(undefined),
match: delta => {
return !!delta.attributes?.underline;
},
@@ -54,9 +48,7 @@ export const UnderlineInlineSpecExtension =
export const StrikeInlineSpecExtension =
InlineSpecExtension<AffineTextAttributes>({
name: 'strike',
schema: z.object({
strike: z.literal(true).optional().nullable().catch(undefined),
}),
schema: z.literal(true).optional().nullable().catch(undefined),
match: delta => {
return !!delta.attributes?.strike;
},
@@ -68,9 +60,7 @@ export const StrikeInlineSpecExtension =
export const CodeInlineSpecExtension =
InlineSpecExtension<AffineTextAttributes>({
name: 'inline-code',
schema: z.object({
code: z.literal(true).optional().nullable().catch(undefined),
}),
schema: z.literal(true).optional().nullable().catch(undefined),
match: delta => {
return !!delta.attributes?.code;
},
@@ -82,9 +72,7 @@ export const CodeInlineSpecExtension =
export const BackgroundInlineSpecExtension =
InlineSpecExtension<AffineTextAttributes>({
name: 'background',
schema: z.object({
background: z.string().optional().nullable().catch(undefined),
}),
schema: z.string().optional().nullable().catch(undefined),
match: delta => {
return !!delta.attributes?.background;
},
@@ -96,9 +84,7 @@ export const BackgroundInlineSpecExtension =
export const ColorInlineSpecExtension =
InlineSpecExtension<AffineTextAttributes>({
name: 'color',
schema: z.object({
color: z.string().optional().nullable().catch(undefined),
}),
schema: z.string().optional().nullable().catch(undefined),
match: delta => {
return !!delta.attributes?.color;
},
@@ -9,7 +9,6 @@
"references": [
{ "path": "../../components" },
{ "path": "../../ext-loader" },
{ "path": "../comment" },
{ "path": "../footnote" },
{ "path": "../latex" },
{ "path": "../link" },
@@ -27,20 +27,18 @@ export const ReferenceInlineSpecExtension =
}
return {
name: 'reference',
schema: z.object({
reference: z
.object({
type: z.enum([
// @deprecated Subpage is deprecated, use LinkedPage instead
'Subpage',
'LinkedPage',
]),
})
.merge(ReferenceInfoSchema)
.optional()
.nullable()
.catch(undefined),
}),
schema: z
.object({
type: z.enum([
// @deprecated Subpage is deprecated, use LinkedPage instead
'Subpage',
'LinkedPage',
]),
})
.merge(ReferenceInfoSchema)
.optional()
.nullable()
.catch(undefined),
match: delta => {
return !!delta.attributes?.reference;
},
@@ -58,8 +58,6 @@ export type AttachmentBlockProps = {
style?: (typeof AttachmentBlockStyles)[number];
footnoteIdentifier: string | null;
comments?: Record<string, boolean>;
} & Omit<GfxCommonBlockProps, 'scale'> &
BlockMeta;
@@ -80,7 +78,6 @@ export const defaultAttachmentProps: AttachmentBlockProps = {
'meta:createdBy': undefined,
'meta:updatedBy': undefined,
footnoteIdentifier: null,
comments: undefined,
};
export const AttachmentBlockSchema = defineBlockSchema({
@@ -28,7 +28,6 @@ export type BookmarkBlockProps = {
url: string;
caption: string | null;
footnoteIdentifier: string | null;
comments?: Record<string, boolean>;
} & LinkPreviewData &
Omit<GfxCommonBlockProps, 'scale'> &
BlockMeta;
@@ -53,7 +52,6 @@ const defaultBookmarkProps: BookmarkBlockProps = {
'meta:updatedBy': undefined,
footnoteIdentifier: null,
comments: undefined,
};
export const BookmarkBlockSchema = defineBlockSchema({
@@ -14,7 +14,6 @@ type CodeBlockProps = {
caption: string;
preview?: boolean;
lineNumber?: boolean;
comments?: Record<string, boolean>;
} & BlockMeta;
export const CodeBlockSchema = defineBlockSchema({
@@ -27,7 +26,6 @@ export const CodeBlockSchema = defineBlockSchema({
caption: '',
preview: undefined,
lineNumber: undefined,
comments: undefined,
'meta:createdAt': undefined,
'meta:createdBy': undefined,
'meta:updatedAt': undefined,
@@ -16,7 +16,6 @@ export type DatabaseBlockProps = {
title: Text;
cells: SerializedCells;
columns: Array<ColumnDataType>;
comments?: Record<string, boolean>;
};
export class DatabaseBlockModel extends BlockModel<DatabaseBlockProps> {}
@@ -28,7 +27,6 @@ export const DatabaseBlockSchema = defineBlockSchema({
title: internal.Text(),
cells: Object.create(null),
columns: [],
comments: undefined,
}),
metadata: {
role: 'hub',
@@ -26,7 +26,6 @@ import { DefaultTheme } from '../../themes/default';
type EdgelessTextProps = {
hasMaxWidth: boolean;
comments?: Record<string, boolean>;
} & Omit<TextStyleProps, 'fontSize'> &
GfxCommonBlockProps;
@@ -55,7 +54,6 @@ export const EdgelessTextBlockSchema = defineBlockSchema({
scale: 1,
rotate: 0,
hasMaxWidth: false,
comments: undefined,
...EdgelessTextZodSchema.parse(undefined),
}),
metadata: {
@@ -30,7 +30,6 @@ export type FrameBlockProps = {
background: Color;
childElementIds?: Record<string, boolean>;
presentationIndex?: string;
comments?: Record<string, boolean>;
} & GfxCompatibleProps;
export const FrameZodSchema = z
@@ -51,7 +50,6 @@ export const FrameBlockSchema = defineBlockSchema({
childElementIds: Object.create(null),
presentationIndex: generateKeyBetweenV2(null, null),
lockedBySelf: false,
comments: undefined,
}),
metadata: {
version: 1,
@@ -19,7 +19,6 @@ export type ImageBlockProps = {
height?: number;
rotate: number;
size?: number;
comments?: Record<string, boolean>;
} & Omit<GfxCommonBlockProps, 'scale'> &
BlockMeta;
@@ -33,7 +32,6 @@ const defaultImageProps: ImageBlockProps = {
lockedBySelf: false,
rotate: 0,
size: -1,
comments: undefined,
'meta:createdAt': undefined,
'meta:createdBy': undefined,
'meta:updatedAt': undefined,
@@ -11,7 +11,6 @@ import {
export type LatexProps = {
latex: string;
comments?: Record<string, boolean>;
} & GfxCommonBlockProps;
export const LatexBlockSchema = defineBlockSchema({
@@ -23,7 +22,6 @@ export const LatexBlockSchema = defineBlockSchema({
scale: 1,
rotate: 0,
latex: '',
comments: undefined,
}),
metadata: {
version: 1,
@@ -16,7 +16,6 @@ export type ListProps = {
checked: boolean;
collapsed: boolean;
order: number | null;
comments?: Record<string, boolean>;
} & BlockMeta;
export const ListBlockSchema = defineBlockSchema({
@@ -30,7 +29,6 @@ export const ListBlockSchema = defineBlockSchema({
// number type only for numbered list
order: null,
comments: undefined,
'meta:createdAt': undefined,
'meta:createdBy': undefined,
'meta:updatedAt': undefined,
@@ -69,7 +69,6 @@ export const NoteBlockSchema = defineBlockSchema({
shadowType: DEFAULT_NOTE_SHADOW,
},
},
comments: undefined,
}),
metadata: {
version: 1,
@@ -92,7 +91,6 @@ export type NoteProps = {
background: Color;
displayMode: NoteDisplayMode;
edgeless: NoteEdgelessProps;
comments?: Record<string, boolean>;
/**
* @deprecated
* use `displayMode` instead
@@ -21,7 +21,6 @@ export type ParagraphProps = {
type: ParagraphType;
text: Text;
collapsed: boolean;
comments?: Record<string, boolean>;
} & BlockMeta;
export const ParagraphBlockSchema = defineBlockSchema({
@@ -30,7 +29,6 @@ export const ParagraphBlockSchema = defineBlockSchema({
type: 'text',
text: internal.Text(),
collapsed: false,
comments: undefined,
'meta:createdAt': undefined,
'meta:createdBy': undefined,
'meta:updatedAt': undefined,
@@ -8,16 +8,14 @@ export type SurfaceRefProps = {
reference: string;
caption: string;
refFlavour: string;
comments?: Record<string, boolean>;
};
export const SurfaceRefBlockSchema = defineBlockSchema({
flavour: 'affine:surface-ref',
props: (): SurfaceRefProps => ({
props: () => ({
reference: '',
caption: '',
refFlavour: '',
comments: undefined,
}),
metadata: {
version: 1,
@@ -29,7 +29,6 @@ export interface TableBlockProps extends BlockMeta {
columns: Record<string, TableColumn>;
// key = `${rowId}:${columnId}`
cells: Record<string, TableCell>;
comments?: Record<string, boolean>;
}
export interface TableCellSerialized {
@@ -52,7 +51,6 @@ export const TableBlockSchema = defineBlockSchema({
rows: {},
columns: {},
cells: {},
comments: undefined,
'meta:createdAt': undefined,
'meta:createdBy': undefined,
'meta:updatedAt': undefined,
@@ -43,7 +43,6 @@ export const ReferenceParamsSchema = z
databaseId: z.string().optional(),
databaseRowId: z.string().optional(),
xywh: SerializedXYWHSchema.optional(),
commentId: z.string().optional(),
})
.partial();
+1 -6
View File
@@ -10,11 +10,7 @@ import {
import type { BlockMeta } from './types';
export type EmbedProps<Props = object> = Props &
GfxCompatibleProps &
BlockMeta & {
comments?: Record<string, boolean>;
};
export type EmbedProps<Props = object> = Props & GfxCompatibleProps & BlockMeta;
export function defineEmbedModel<
Props extends object,
@@ -56,7 +52,6 @@ export function createEmbedBlockSchema<
xywh: '[0,0,0,0]',
lockedBySelf: false,
rotate: 0,
comments: undefined,
'meta:createdAt': undefined,
'meta:updatedAt': undefined,
'meta:createdBy': undefined,
@@ -1,118 +0,0 @@
import { DividerBlockModel } from '@blocksuite/affine-model';
import { DisposableGroup } from '@blocksuite/global/disposable';
import {
BlockSelection,
LifeCycleWatcher,
TextSelection,
} from '@blocksuite/std';
import type { BaseSelection, BlockModel } from '@blocksuite/store';
import { signal } from '@preact/signals-core';
import { getSelectedBlocksCommand } from '../../commands';
import { ImageSelection } from '../../selection';
import { matchModels } from '../../utils';
import { type CommentId, CommentProviderIdentifier } from './comment-provider';
import { findCommentedBlocks } from './utils';
export class BlockCommentManager extends LifeCycleWatcher {
static override key = 'block-comment-manager';
private readonly _highlightedCommentId$ = signal<CommentId | null>(null);
private readonly _disposables = new DisposableGroup();
private get _provider() {
return this.std.getOptional(CommentProviderIdentifier);
}
isBlockCommentHighlighted(
block: BlockModel<{ comments?: Record<CommentId, boolean> }>
) {
const comments = block.props.comments;
if (!comments) return false;
return (
this._highlightedCommentId$.value !== null &&
Object.keys(comments).includes(this._highlightedCommentId$.value)
);
}
override mounted() {
const provider = this._provider;
if (!provider) return;
this._disposables.add(provider.onCommentAdded(this._handleAddComment));
this._disposables.add(
provider.onCommentDeleted(this._handleDeleteAndResolve)
);
this._disposables.add(
provider.onCommentResolved(this._handleDeleteAndResolve)
);
this._disposables.add(
provider.onCommentHighlighted(this._handleHighlightComment)
);
}
override unmounted() {
this._disposables.dispose();
}
private readonly _handleAddComment = (
id: CommentId,
selections: BaseSelection[]
) => {
const blocksFromTextRange = selections
.filter((s): s is TextSelection => s.is(TextSelection))
.map(s => {
const [_, { selectedBlocks }] = this.std.command.exec(
getSelectedBlocksCommand,
{
textSelection: s,
}
);
if (!selectedBlocks) return [];
return selectedBlocks.map(b => b.model).filter(m => !m.text);
});
const needCommentBlocks = [
...blocksFromTextRange.flat(),
...selections
.filter(s => s instanceof BlockSelection || s instanceof ImageSelection)
.map(({ blockId }) => this.std.store.getModelById(blockId))
.filter(
(m): m is BlockModel =>
m !== null && !matchModels(m, [DividerBlockModel])
),
];
if (needCommentBlocks.length === 0) return;
this.std.store.withoutTransact(() => {
needCommentBlocks.forEach(block => {
const comments = (
'comments' in block.props &&
typeof block.props.comments === 'object' &&
block.props.comments !== null
? block.props.comments
: {}
) as Record<CommentId, boolean>;
this.std.store.updateBlock(block, {
comments: { [id]: true, ...comments },
});
});
});
};
private readonly _handleDeleteAndResolve = (id: CommentId) => {
const commentedBlocks = findCommentedBlocks(this.std.store, id);
this.std.store.withoutTransact(() => {
commentedBlocks.forEach(block => {
delete block.props.comments[id];
});
});
};
private readonly _handleHighlightComment = (id: CommentId | null) => {
this._highlightedCommentId$.value = id;
};
}
@@ -1,41 +0,0 @@
import { createIdentifier } from '@blocksuite/global/di';
import type { DisposableMember } from '@blocksuite/global/disposable';
import type { BaseSelection, ExtensionType } from '@blocksuite/store';
export type CommentId = string;
/**
* The `CommentProvider` is an interface used to connect external comment services
* with in-editor comment operations and rendering.
* All comment-related actions within the editor are routed through
* this interface to make external requests, and the editor is notified via callbacks.
* In essence, it follows the flow: BlockSuite -> AFFiNE -> BlockSuite.
*/
export interface CommentProvider {
addComment: (selections: BaseSelection[]) => void;
resolveComment: (id: CommentId) => void;
highlightComment: (id: CommentId | null) => void;
getComments: () => CommentId[];
onCommentAdded: (
callback: (id: CommentId, selections: BaseSelection[]) => void
) => DisposableMember;
onCommentResolved: (callback: (id: CommentId) => void) => DisposableMember;
onCommentDeleted: (callback: (id: CommentId) => void) => DisposableMember;
onCommentHighlighted: (
callback: (id: CommentId | null) => void
) => DisposableMember;
}
export const CommentProviderIdentifier =
createIdentifier<CommentProvider>('comment-provider');
export const CommentProviderExtension = (
provider: CommentProvider
): ExtensionType => {
return {
setup: di => {
di.addImpl(CommentProviderIdentifier, provider);
},
};
};
@@ -1,3 +0,0 @@
export * from './block-comment-manager';
export * from './comment-provider';
export * from './utils';
@@ -1,45 +0,0 @@
import { CommentIcon } from '@blocksuite/icons/lit';
import { BlockSelection } from '@blocksuite/std';
import type { BlockModel, Store } from '@blocksuite/store';
import type { ToolbarAction } from '../toolbar-service';
import { type CommentId, CommentProviderIdentifier } from './comment-provider';
export function findCommentedBlocks(store: Store, commentId: CommentId) {
type CommentedBlock = BlockModel<{ comments: Record<CommentId, boolean> }>;
return store.getAllModels().filter((block): block is CommentedBlock => {
return (
'comments' in block.props &&
typeof block.props.comments === 'object' &&
block.props.comments !== null &&
commentId in block.props.comments
);
});
}
export const blockCommentToolbarButton: Omit<ToolbarAction, 'id'> = {
tooltip: 'Comment',
when: ({ std }) => !!std.getOptional(CommentProviderIdentifier),
icon: CommentIcon(),
run: ctx => {
const commentProvider = ctx.std.getOptional(CommentProviderIdentifier);
if (!commentProvider) return;
const selections = ctx.selection.value;
const model = ctx.getCurrentModel();
if (selections.length > 1) {
commentProvider.addComment(selections);
} else if (model) {
commentProvider.addComment([
new BlockSelection({
blockId: model.id,
}),
]);
} else if (selections.length === 1) {
commentProvider.addComment(selections);
} else {
return;
}
},
};
@@ -21,7 +21,6 @@ export interface BlockSuiteFlags {
enable_table_virtual_scroll: boolean;
enable_turbo_renderer: boolean;
enable_dom_renderer: boolean;
enable_comment: boolean;
}
export class FeatureFlagService extends StoreExtension {
@@ -47,7 +46,6 @@ export class FeatureFlagService extends StoreExtension {
enable_table_virtual_scroll: false,
enable_turbo_renderer: false,
enable_dom_renderer: false,
enable_comment: false,
});
setFlag(key: keyof BlockSuiteFlags, value: boolean) {
@@ -1,7 +1,6 @@
export * from './auto-clear-selection-service';
export * from './block-meta-service';
export * from './citation-service';
export * from './comment-service';
export * from './doc-display-meta-service';
export * from './doc-mode-service';
export * from './drag-handle-config';
+4 -8
View File
@@ -35,31 +35,27 @@ export type IndentContext = {
type: 'indent' | 'dedent';
};
export type AffineTextStyleAttributes = {
export interface AffineTextAttributes {
bold?: true | null;
italic?: true | null;
underline?: true | null;
strike?: true | null;
code?: true | null;
color?: string | null;
background?: string | null;
};
export type AffineTextAttributes = AffineTextStyleAttributes & {
link?: string | null;
reference?:
| ({
type: 'Subpage' | 'LinkedPage';
} & ReferenceInfo)
| null;
background?: string | null;
color?: string | null;
latex?: string | null;
footnote?: FootNote | null;
mention?: {
member: string;
notification?: string;
} | null;
[key: `comment-${string}`]: boolean | null;
};
}
export type AffineInlineEditor = InlineEditor<AffineTextAttributes>;

Some files were not shown because too many files have changed in this diff Show More