mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-08 10:33:44 +00:00
Compare commits
45 Commits
0.23.0-bet
...
v0.22.5
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
998f9e7a20 | ||
|
|
7879e12718 | ||
|
|
81f542a4da | ||
|
|
532ea6af07 | ||
|
|
a7aa761e43 | ||
|
|
062537c2cf | ||
|
|
2ea3c3da9d | ||
|
|
4fd42a8461 | ||
|
|
32c40bbf09 | ||
|
|
5d8ee51e8c | ||
|
|
b2bf591463 | ||
|
|
2ae3c3e2cd | ||
|
|
4641b080f2 | ||
|
|
aa7edb7255 | ||
|
|
3e03599d11 | ||
|
|
a21f1c943e | ||
|
|
a59448ec4b | ||
|
|
f761cbd964 | ||
|
|
d768ad4af0 | ||
|
|
8ce85f708d | ||
|
|
a66096cdf9 | ||
|
|
facf6ee28b | ||
|
|
bcd6a70b59 | ||
|
|
423c5bd711 | ||
|
|
3d12bb2adf | ||
|
|
a2810f3f61 | ||
|
|
7ed72ed1d0 | ||
|
|
2aa5c13082 | ||
|
|
6a04fbe335 | ||
|
|
0326da0806 | ||
|
|
6e9487a9e1 | ||
|
|
d49a069351 | ||
|
|
f600a1534a | ||
|
|
eb0ee52706 | ||
|
|
e8bc8f2d63 | ||
|
|
6e034185cf | ||
|
|
2be3f84196 | ||
|
|
f46d288b1b | ||
|
|
9529adf33e | ||
|
|
03aeb44dc9 | ||
|
|
c9aad0d55e | ||
|
|
29ae6afe71 | ||
|
|
32787bc88b | ||
|
|
bbafce2c40 | ||
|
|
f7f69c3bc4 |
@@ -597,11 +597,6 @@
|
||||
"type": "string",
|
||||
"description": "Allowed version range of the app that allowed to access the server. Requires 'client/versionControl.enabled' to be true to take effect.\n@default \">=0.20.0\"",
|
||||
"default": ">=0.20.0"
|
||||
},
|
||||
"allowGuestDemoWorkspace": {
|
||||
"type": "boolean",
|
||||
"description": "Allow guests to access demo workspace.\n@default true",
|
||||
"default": true
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
2
.github/actions/prepare-release/action.yml
vendored
2
.github/actions/prepare-release/action.yml
vendored
@@ -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
|
||||
|
||||
2
.github/workflows/release-mobile.yml
vendored
2
.github/workflows/release-mobile.yml
vendored
@@ -124,7 +124,6 @@ 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
|
||||
@@ -132,6 +131,7 @@ 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 }}
|
||||
|
||||
@@ -48,6 +48,7 @@
|
||||
"@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:*",
|
||||
@@ -173,6 +174,7 @@
|
||||
"./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",
|
||||
@@ -283,6 +285,7 @@
|
||||
"./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,6 +33,7 @@ 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';
|
||||
@@ -95,6 +96,7 @@ export function getInternalViewExtensions() {
|
||||
RootViewExtension,
|
||||
|
||||
// Inline
|
||||
InlineCommentViewExtension,
|
||||
FootnoteViewExtension,
|
||||
LinkViewExtension,
|
||||
ReferenceViewExtension,
|
||||
|
||||
1
blocksuite/affine/all/src/foundation/clipboard.ts
Normal file
1
blocksuite/affine/all/src/foundation/clipboard.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from '@blocksuite/affine-foundation/clipboard';
|
||||
1
blocksuite/affine/all/src/inlines/comment/index.ts
Normal file
1
blocksuite/affine/all/src/inlines/comment/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from '@blocksuite/affine-inline-comment';
|
||||
@@ -45,6 +45,7 @@
|
||||
{ "path": "../gfx/template" },
|
||||
{ "path": "../gfx/text" },
|
||||
{ "path": "../gfx/turbo-renderer" },
|
||||
{ "path": "../inlines/comment" },
|
||||
{ "path": "../inlines/footnote" },
|
||||
{ "path": "../inlines/latex" },
|
||||
{ "path": "../inlines/link" },
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
AttachmentBlockStyles,
|
||||
} from '@blocksuite/affine-model';
|
||||
import {
|
||||
BlockCommentManager,
|
||||
CitationProvider,
|
||||
DocModeProvider,
|
||||
FileSizeLimitProvider,
|
||||
@@ -92,6 +93,14 @@ 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)
|
||||
@@ -499,6 +508,7 @@ export class AttachmentBlockComponent extends CaptionedBlockComponent<Attachment
|
||||
class=${classMap({
|
||||
'affine-attachment-container': true,
|
||||
focused: this.selected$.value,
|
||||
'comment-highlighted': this.isCommentHighlighted,
|
||||
})}
|
||||
style=${this.containerStyleMap}
|
||||
>
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
} from '@blocksuite/affine-shared/consts';
|
||||
import {
|
||||
ActionPlacement,
|
||||
blockCommentToolbarButton,
|
||||
type ToolbarAction,
|
||||
type ToolbarActionGroup,
|
||||
type ToolbarModuleConfig,
|
||||
@@ -240,6 +241,10 @@ const builtinToolbarConfig = {
|
||||
replaceAction,
|
||||
downloadAction,
|
||||
captionAction,
|
||||
{
|
||||
id: 'f.comment',
|
||||
...blockCommentToolbarButton,
|
||||
},
|
||||
{
|
||||
placement: ActionPlacement.More,
|
||||
id: 'a.clipboard',
|
||||
|
||||
@@ -15,6 +15,10 @@ export const styles = css`
|
||||
}
|
||||
}
|
||||
|
||||
.affine-attachment-container.comment-highlighted {
|
||||
outline: 2px solid ${unsafeCSSVarV2('block/comment/highlightUnderline')};
|
||||
}
|
||||
|
||||
.affine-attachment-card {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
|
||||
@@ -8,6 +8,7 @@ import type {
|
||||
} from '@blocksuite/affine-model';
|
||||
import { ImageProxyService } from '@blocksuite/affine-shared/adapters';
|
||||
import {
|
||||
BlockCommentManager,
|
||||
CitationProvider,
|
||||
DocModeProvider,
|
||||
LinkPreviewServiceIdentifier,
|
||||
@@ -128,6 +129,14 @@ 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,6 +45,7 @@ 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(
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
} from '@blocksuite/affine-shared/consts';
|
||||
import {
|
||||
ActionPlacement,
|
||||
blockCommentToolbarButton,
|
||||
EmbedIframeService,
|
||||
EmbedOptionProvider,
|
||||
type LinkEventType,
|
||||
@@ -288,6 +289,10 @@ const builtinToolbarConfig = {
|
||||
},
|
||||
} satisfies ToolbarActionGroup<ToolbarAction>,
|
||||
captionAction,
|
||||
{
|
||||
id: 'e.comment',
|
||||
...blockCommentToolbarButton,
|
||||
},
|
||||
{
|
||||
placement: ActionPlacement.More,
|
||||
id: 'a.clipboard',
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { unsafeCSSVar } from '@blocksuite/affine-shared/theme';
|
||||
import { unsafeCSSVar, unsafeCSSVarV2 } from '@blocksuite/affine-shared/theme';
|
||||
import { baseTheme } from '@toeverything/theme';
|
||||
import { css, unsafeCSS } from 'lit';
|
||||
|
||||
@@ -158,6 +158,10 @@ 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,6 +13,7 @@
|
||||
"@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,3 +1,4 @@
|
||||
import { CommentInlineSpecExtension } from '@blocksuite/affine-inline-comment';
|
||||
import { LatexInlineSpecExtension } from '@blocksuite/affine-inline-latex';
|
||||
import { LinkInlineSpecExtension } from '@blocksuite/affine-inline-link';
|
||||
import {
|
||||
@@ -20,7 +21,9 @@ import { z } from 'zod';
|
||||
export const CodeBlockUnitSpecExtension =
|
||||
InlineSpecExtension<AffineTextAttributes>({
|
||||
name: 'code-block-unit',
|
||||
schema: z.undefined(),
|
||||
schema: z.object({
|
||||
'code-block-uint': z.undefined(),
|
||||
}),
|
||||
match: () => true,
|
||||
renderer: ({ delta }) => {
|
||||
return html`<affine-code-unit .delta=${delta}></affine-code-unit>`;
|
||||
@@ -42,5 +45,6 @@ export const CodeBlockInlineManagerExtension =
|
||||
LatexInlineSpecExtension.identifier,
|
||||
LinkInlineSpecExtension.identifier,
|
||||
CodeBlockUnitSpecExtension.identifier,
|
||||
CommentInlineSpecExtension.identifier,
|
||||
],
|
||||
});
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
EDGELESS_TOP_CONTENTEDITABLE_SELECTOR,
|
||||
} from '@blocksuite/affine-shared/consts';
|
||||
import {
|
||||
BlockCommentManager,
|
||||
DocModeProvider,
|
||||
NotificationProvider,
|
||||
} from '@blocksuite/affine-shared/services';
|
||||
@@ -390,6 +391,14 @@ 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;
|
||||
@@ -413,6 +422,7 @@ 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,9 +7,10 @@ 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 { NumberedListIcon } from '@blocksuite/icons/lit';
|
||||
import { CommentIcon, NumberedListIcon } from '@blocksuite/icons/lit';
|
||||
import { BlockSelection } from '@blocksuite/std';
|
||||
import { html } from 'lit';
|
||||
import { ifDefined } from 'lit/directives/if-defined.js';
|
||||
@@ -113,6 +114,47 @@ 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,4 +1,5 @@
|
||||
import { scrollbarStyle } from '@blocksuite/affine-shared/styles';
|
||||
import { unsafeCSSVarV2 } from '@blocksuite/affine-shared/theme';
|
||||
import { css } from 'lit';
|
||||
|
||||
export const codeBlockStyles = css`
|
||||
@@ -20,6 +21,10 @@ 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,6 +10,7 @@
|
||||
{ "path": "../../components" },
|
||||
{ "path": "../../ext-loader" },
|
||||
{ "path": "../../gfx/turbo-renderer" },
|
||||
{ "path": "../../inlines/comment" },
|
||||
{ "path": "../../inlines/latex" },
|
||||
{ "path": "../../inlines/link" },
|
||||
{ "path": "../../inlines/preset" },
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
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';
|
||||
@@ -72,6 +73,12 @@ 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 = () => {
|
||||
@@ -134,6 +141,7 @@ 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,6 +10,8 @@ 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,
|
||||
@@ -34,11 +36,12 @@ 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 } from '@blocksuite/std';
|
||||
import { type BlockComponent, BlockSelection } from '@blocksuite/std';
|
||||
import { RANGE_SYNC_EXCLUDE_ATTR } from '@blocksuite/std/inline';
|
||||
import { Slice } from '@blocksuite/store';
|
||||
import { autoUpdate } from '@floating-ui/dom';
|
||||
@@ -82,6 +85,18 @@ 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',
|
||||
@@ -297,6 +312,14 @@ 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,6 +11,7 @@ import {
|
||||
} from '@blocksuite/affine-shared/consts';
|
||||
import {
|
||||
ActionPlacement,
|
||||
blockCommentToolbarButton,
|
||||
DocDisplayMetaProvider,
|
||||
EditorSettingProvider,
|
||||
type LinkEventType,
|
||||
@@ -305,6 +306,10 @@ const builtinToolbarConfig = {
|
||||
},
|
||||
} satisfies ToolbarActionGroup<ToolbarAction>,
|
||||
captionAction,
|
||||
{
|
||||
id: 'e.comment',
|
||||
...blockCommentToolbarButton,
|
||||
},
|
||||
{
|
||||
placement: ActionPlacement.More,
|
||||
id: 'a.clipboard',
|
||||
|
||||
@@ -338,6 +338,7 @@ 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,6 +15,10 @@ 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,6 +16,7 @@ import {
|
||||
import { REFERENCE_NODE } from '@blocksuite/affine-shared/consts';
|
||||
import {
|
||||
ActionPlacement,
|
||||
blockCommentToolbarButton,
|
||||
EditorSettingProvider,
|
||||
type LinkEventType,
|
||||
type OpenDocMode,
|
||||
@@ -225,6 +226,10 @@ const builtinToolbarConfig = {
|
||||
openDocActionGroup,
|
||||
conversionsActionGroup,
|
||||
captionAction,
|
||||
{
|
||||
id: 'e.comment',
|
||||
...blockCommentToolbarButton,
|
||||
},
|
||||
{
|
||||
placement: ActionPlacement.More,
|
||||
id: 'a.clipboard',
|
||||
|
||||
@@ -232,6 +232,7 @@ 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,6 +57,9 @@ 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,17 +2,20 @@ import {
|
||||
CaptionedBlockComponent,
|
||||
SelectedStyle,
|
||||
} from '@blocksuite/affine-components/caption';
|
||||
import type { EmbedCardStyle } from '@blocksuite/affine-model';
|
||||
import type { EmbedCardStyle, EmbedProps } from '@blocksuite/affine-model';
|
||||
import {
|
||||
EMBED_CARD_HEIGHT,
|
||||
EMBED_CARD_MIN_WIDTH,
|
||||
EMBED_CARD_WIDTH,
|
||||
} from '@blocksuite/affine-shared/consts';
|
||||
import { DocModeProvider } from '@blocksuite/affine-shared/services';
|
||||
import {
|
||||
BlockCommentManager,
|
||||
DocModeProvider,
|
||||
} from '@blocksuite/affine-shared/services';
|
||||
import { unsafeCSSVarV2 } from '@blocksuite/affine-shared/theme';
|
||||
import { findAncestorModel } from '@blocksuite/affine-shared/utils';
|
||||
import type { BlockService } from '@blocksuite/std';
|
||||
import {
|
||||
type GfxCompatibleProps,
|
||||
GfxViewInteractionExtension,
|
||||
type ResizeConstraint,
|
||||
} from '@blocksuite/std/gfx';
|
||||
@@ -25,7 +28,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<GfxCompatibleProps> = BlockModel<GfxCompatibleProps>,
|
||||
Model extends BlockModel<EmbedProps> = BlockModel<EmbedProps>,
|
||||
Service extends BlockService = BlockService,
|
||||
WidgetName extends string = string,
|
||||
> extends CaptionedBlockComponent<Model, Service, WidgetName> {
|
||||
@@ -59,6 +62,14 @@ export class EmbedBlockComponent<
|
||||
*/
|
||||
protected embedContainerStyle: StyleInfo = {};
|
||||
|
||||
get isCommentHighlighted() {
|
||||
return (
|
||||
this.std
|
||||
.getOptional(BlockCommentManager)
|
||||
?.isBlockCommentHighlighted(this.model) ?? false
|
||||
);
|
||||
}
|
||||
|
||||
renderEmbed = (content: () => TemplateResult) => {
|
||||
if (
|
||||
this._cardStyle === 'horizontal' ||
|
||||
@@ -90,6 +101,11 @@ export class EmbedBlockComponent<
|
||||
style=${styleMap({
|
||||
height: `${this._cardHeight}px`,
|
||||
width: '100%',
|
||||
...(this.isCommentHighlighted
|
||||
? {
|
||||
border: `2px solid ${unsafeCSSVarV2('block/comment/highlightUnderline')}`,
|
||||
}
|
||||
: {}),
|
||||
...this.embedContainerStyle,
|
||||
})}
|
||||
>
|
||||
|
||||
@@ -57,6 +57,11 @@ 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,4 +1,5 @@
|
||||
import { EdgelessLegacySlotIdentifier } from '@blocksuite/affine-block-surface';
|
||||
import type { EmbedProps } from '@blocksuite/affine-model';
|
||||
import { Bound } from '@blocksuite/global/gfx';
|
||||
import {
|
||||
blockComponentSymbol,
|
||||
@@ -7,16 +8,13 @@ import {
|
||||
GfxElementSymbol,
|
||||
toGfxBlockComponent,
|
||||
} from '@blocksuite/std';
|
||||
import type {
|
||||
GfxBlockElementModel,
|
||||
GfxCompatibleProps,
|
||||
} from '@blocksuite/std/gfx';
|
||||
import type { GfxBlockElementModel } 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<GfxCompatibleProps>,
|
||||
Model extends GfxBlockElementModel<EmbedProps>,
|
||||
Service extends BlockService,
|
||||
WidgetName extends string,
|
||||
B extends typeof EmbedBlockComponent<Model, Service, WidgetName>,
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
} from '@blocksuite/affine-shared/consts';
|
||||
import {
|
||||
ActionPlacement,
|
||||
blockCommentToolbarButton,
|
||||
EmbedOptionProvider,
|
||||
type LinkEventType,
|
||||
type ToolbarAction,
|
||||
@@ -348,6 +349,10 @@ function createBuiltinToolbarConfigForExternal(
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'e.comment',
|
||||
...blockCommentToolbarButton,
|
||||
},
|
||||
{
|
||||
placement: ActionPlacement.More,
|
||||
id: 'a.clipboard',
|
||||
|
||||
@@ -18,6 +18,7 @@ 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';
|
||||
|
||||
@@ -76,6 +77,10 @@ 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);
|
||||
@@ -364,7 +369,13 @@ export class ImageBlockPageComponent extends SignalWatcher(
|
||||
const { loading, error, icon, description, needUpload } = this.state;
|
||||
|
||||
return html`
|
||||
<div class="resizable-img" style=${styleMap(imageSize)}>
|
||||
<div
|
||||
class=${classMap({
|
||||
'resizable-img': true,
|
||||
'comment-highlighted': this.block.isCommentHighlighted,
|
||||
})}
|
||||
style=${styleMap(imageSize)}
|
||||
>
|
||||
<img
|
||||
class="drag-target"
|
||||
draggable="false"
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { ImageBlockModel } from '@blocksuite/affine-model';
|
||||
import {
|
||||
ActionPlacement,
|
||||
blockCommentToolbarButton,
|
||||
type ToolbarModuleConfig,
|
||||
ToolbarModuleExtension,
|
||||
} from '@blocksuite/affine-shared/services';
|
||||
@@ -49,6 +50,10 @@ const builtinToolbarConfig = {
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'c.comment',
|
||||
...blockCommentToolbarButton,
|
||||
},
|
||||
{
|
||||
placement: ActionPlacement.More,
|
||||
id: 'a.clipboard',
|
||||
@@ -141,6 +146,10 @@ const builtinSurfaceToolbarConfig = {
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'c.comment',
|
||||
...blockCommentToolbarButton,
|
||||
},
|
||||
],
|
||||
|
||||
when: ctx => ctx.getSurfaceModelsByType(ImageBlockModel).length === 1,
|
||||
|
||||
@@ -5,7 +5,10 @@ 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 { ToolbarRegistryIdentifier } from '@blocksuite/affine-shared/services';
|
||||
import {
|
||||
BlockCommentManager,
|
||||
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';
|
||||
@@ -65,6 +68,14 @@ 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,6 +8,7 @@ import {
|
||||
EDGELESS_TOP_CONTENTEDITABLE_SELECTOR,
|
||||
} from '@blocksuite/affine-shared/consts';
|
||||
import {
|
||||
BlockCommentManager,
|
||||
CitationProvider,
|
||||
DocModeProvider,
|
||||
} from '@blocksuite/affine-shared/services';
|
||||
@@ -107,6 +108,14 @@ 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>(
|
||||
@@ -268,7 +277,10 @@ export class ParagraphBlockComponent extends CaptionedBlockComponent<ParagraphBl
|
||||
}
|
||||
</style>
|
||||
<div
|
||||
class="affine-paragraph-block-container"
|
||||
class=${classMap({
|
||||
'affine-paragraph-block-container': true,
|
||||
'highlight-comment': this.isCommentHighlighted,
|
||||
})}
|
||||
data-has-collapsed-siblings="${collapsedSiblings.length > 0}"
|
||||
>
|
||||
<div
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { unsafeCSSVarV2 } from '@blocksuite/affine-shared/theme';
|
||||
import { css } from 'lit';
|
||||
|
||||
export const paragraphBlockStyles = css`
|
||||
@@ -15,6 +16,11 @@ 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,6 +9,7 @@ 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 {
|
||||
@@ -19,6 +20,10 @@ import {
|
||||
isFormatSupported,
|
||||
textFormatConfigs,
|
||||
} from '@blocksuite/affine-inline-preset';
|
||||
import {
|
||||
EmbedLinkedDocBlockSchema,
|
||||
EmbedSyncedDocBlockSchema,
|
||||
} from '@blocksuite/affine-model';
|
||||
import { textConversionConfigs } from '@blocksuite/affine-rich-text';
|
||||
import {
|
||||
copySelectedModelsCommand,
|
||||
@@ -37,8 +42,10 @@ import type {
|
||||
ToolbarActionGroup,
|
||||
ToolbarModuleConfig,
|
||||
} from '@blocksuite/affine-shared/services';
|
||||
import { ActionPlacement } from '@blocksuite/affine-shared/services';
|
||||
import type { AffineTextAttributes } from '@blocksuite/affine-shared/types';
|
||||
import {
|
||||
ActionPlacement,
|
||||
blockCommentToolbarButton,
|
||||
} from '@blocksuite/affine-shared/services';
|
||||
import { tableViewMeta } from '@blocksuite/data-view/view-presets';
|
||||
import {
|
||||
CopyIcon,
|
||||
@@ -47,7 +54,11 @@ import {
|
||||
DuplicateIcon,
|
||||
LinkedPageIcon,
|
||||
} from '@blocksuite/icons/lit';
|
||||
import { type BlockComponent, BlockSelection } from '@blocksuite/std';
|
||||
import {
|
||||
type BlockComponent,
|
||||
BlockSelection,
|
||||
BlockViewIdentifier,
|
||||
} from '@blocksuite/std';
|
||||
import { toDraftModel } from '@blocksuite/store';
|
||||
import { html } from 'lit';
|
||||
import { repeat } from 'lit/directives/repeat.js';
|
||||
@@ -140,7 +151,7 @@ const highlightActionGroup = {
|
||||
id: 'c.highlight',
|
||||
when: ({ chain }) => isFormatSupported(chain).run()[0],
|
||||
content({ chain }) {
|
||||
const updateHighlight = (styles: AffineTextAttributes) => {
|
||||
const updateHighlight = (styles: HighlightType) => {
|
||||
const payload = { styles };
|
||||
chain
|
||||
.try(chain => [
|
||||
@@ -161,7 +172,7 @@ const highlightActionGroup = {
|
||||
} as const satisfies ToolbarAction;
|
||||
|
||||
const turnIntoDatabase = {
|
||||
id: 'd.convert-to-database',
|
||||
id: 'e.convert-to-database',
|
||||
tooltip: 'Create Table',
|
||||
icon: DatabaseTableViewIcon(),
|
||||
when({ chain }) {
|
||||
@@ -208,10 +219,21 @@ const turnIntoDatabase = {
|
||||
} as const satisfies ToolbarAction;
|
||||
|
||||
const turnIntoLinkedDoc = {
|
||||
id: 'e.convert-to-linked-doc',
|
||||
id: 'f.convert-to-linked-doc',
|
||||
tooltip: 'Create Linked Doc',
|
||||
icon: LinkedPageIcon(),
|
||||
when({ chain }) {
|
||||
when({ chain, std }) {
|
||||
const supportFlavours = [
|
||||
EmbedLinkedDocBlockSchema,
|
||||
EmbedSyncedDocBlockSchema,
|
||||
].map(schema => schema.model.flavour);
|
||||
if (
|
||||
supportFlavours.some(
|
||||
flavour => !std.getOptional(BlockViewIdentifier(flavour))
|
||||
)
|
||||
)
|
||||
return false;
|
||||
|
||||
const [ok, { selectedModels }] = chain
|
||||
.pipe(getSelectedModelsCommand, {
|
||||
types: ['block', 'text'],
|
||||
@@ -273,6 +295,10 @@ export const builtinToolbarConfig = {
|
||||
highlightActionGroup,
|
||||
turnIntoDatabase,
|
||||
turnIntoLinkedDoc,
|
||||
{
|
||||
id: 'g.comment',
|
||||
...blockCommentToolbarButton,
|
||||
},
|
||||
{
|
||||
placement: ActionPlacement.More,
|
||||
id: 'a.clipboard',
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
} from '@blocksuite/affine-shared/commands';
|
||||
import {
|
||||
ActionPlacement,
|
||||
blockCommentToolbarButton,
|
||||
type ToolbarModuleConfig,
|
||||
} from '@blocksuite/affine-shared/services';
|
||||
import { CaptionIcon, CopyIcon, DeleteIcon } from '@blocksuite/icons/lit';
|
||||
@@ -61,6 +62,10 @@ export const surfaceRefToolbarModuleConfig: ToolbarModuleConfig = {
|
||||
surfaceRefBlock.captionElement.show();
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'e.comment',
|
||||
...blockCommentToolbarButton,
|
||||
},
|
||||
{
|
||||
id: 'a.clipboard',
|
||||
placement: ActionPlacement.More,
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
type SurfaceRefBlockModel,
|
||||
} from '@blocksuite/affine-model';
|
||||
import {
|
||||
BlockCommentManager,
|
||||
DocModeProvider,
|
||||
EditPropsStore,
|
||||
type OpenDocMode,
|
||||
@@ -76,6 +77,10 @@ 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;
|
||||
@@ -137,6 +142,14 @@ 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 })];
|
||||
@@ -456,6 +469,7 @@ 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 { AffineTextAttributes } from '@blocksuite/affine-shared/types';
|
||||
import type { AffineTextStyleAttributes } from '@blocksuite/affine-shared/types';
|
||||
import { PropTypes, requiredProperties } from '@blocksuite/std';
|
||||
import { LitElement } from 'lit';
|
||||
import { property } from 'lit/decorators.js';
|
||||
@@ -20,7 +20,10 @@ const colors = [
|
||||
'grey',
|
||||
] as const;
|
||||
|
||||
type HighlightType = 'color' | 'background';
|
||||
export type HighlightType = Pick<
|
||||
AffineTextStyleAttributes,
|
||||
'color' | 'background'
|
||||
>;
|
||||
|
||||
// TODO(@fundon): these recent settings should be added to the dropdown menu
|
||||
// tests/blocksutie/e2e/format-bar.spec.ts#253
|
||||
@@ -33,13 +36,13 @@ type HighlightType = 'color' | 'background';
|
||||
})
|
||||
export class HighlightDropdownMenu extends LitElement {
|
||||
@property({ attribute: false })
|
||||
accessor updateHighlight!: (styles: AffineTextAttributes) => void;
|
||||
accessor updateHighlight!: (styles: HighlightType) => void;
|
||||
|
||||
private readonly _update = (value: string | null, type: HighlightType) => {
|
||||
private readonly _update = (style: HighlightType) => {
|
||||
// latestHighlightColor = value;
|
||||
// latestHighlightType = type;
|
||||
|
||||
this.updateHighlight({ [`${type}`]: value });
|
||||
this.updateHighlight(style);
|
||||
};
|
||||
|
||||
override render() {
|
||||
@@ -71,7 +74,7 @@ export class HighlightDropdownMenu extends LitElement {
|
||||
return html`
|
||||
<editor-menu-action
|
||||
data-testid="foreground-${color}"
|
||||
@click=${() => this._update(value, 'color')}
|
||||
@click=${() => this._update({ color: value })}
|
||||
>
|
||||
<affine-text-duotone-icon
|
||||
style=${styleMap({
|
||||
@@ -92,7 +95,7 @@ export class HighlightDropdownMenu extends LitElement {
|
||||
return html`
|
||||
<editor-menu-action
|
||||
data-testid="background-${color}"
|
||||
@click=${() => this._update(value, 'background')}
|
||||
@click=${() => this._update({ background: value })}
|
||||
>
|
||||
<affine-text-duotone-icon
|
||||
style=${styleMap({
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
import { NoteBlockModel } from '@blocksuite/affine-model';
|
||||
import { DocModeProvider } from '@blocksuite/affine-shared/services';
|
||||
import { isInsideEdgelessEditor } from '@blocksuite/affine-shared/utils';
|
||||
import {
|
||||
isInsideEdgelessEditor,
|
||||
matchModels,
|
||||
} from '@blocksuite/affine-shared/utils';
|
||||
import type { Constructor } from '@blocksuite/global/utils';
|
||||
import { GfxControllerIdentifier } from '@blocksuite/std/gfx';
|
||||
import {
|
||||
GfxBlockElementModel,
|
||||
GfxControllerIdentifier,
|
||||
} from '@blocksuite/std/gfx';
|
||||
import type { BlockModel } from '@blocksuite/store';
|
||||
import type { LitElement, TemplateResult } from 'lit';
|
||||
|
||||
@@ -72,6 +79,20 @@ 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,6 +32,7 @@
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./clipboard": "./src/clipboard.ts",
|
||||
"./store": "./src/store.ts",
|
||||
"./view": "./src/view.ts"
|
||||
},
|
||||
|
||||
@@ -43,7 +43,7 @@ const imageClipboardConfigs = [
|
||||
});
|
||||
});
|
||||
|
||||
const PlainTextClipboardConfig = ClipboardAdapterConfigExtension({
|
||||
export const PlainTextClipboardConfig = ClipboardAdapterConfigExtension({
|
||||
mimeType: 'text/plain',
|
||||
adapter: MixTextAdapter,
|
||||
priority: 70,
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
} from '@blocksuite/affine-ext-loader';
|
||||
import {
|
||||
AutoClearSelectionService,
|
||||
BlockCommentManager,
|
||||
CitationService,
|
||||
DefaultOpenDocExtension,
|
||||
DNDAPIExtension,
|
||||
@@ -78,6 +79,7 @@ export class FoundationViewExtension extends ViewExtensionProvider<FoundationVie
|
||||
LinkPreviewCache,
|
||||
LinkPreviewService,
|
||||
CitationService,
|
||||
BlockCommentManager,
|
||||
]);
|
||||
context.register(clipboardConfigs);
|
||||
if (this.isEdgeless(context.scope)) {
|
||||
|
||||
46
blocksuite/affine/inlines/comment/package.json
Normal file
46
blocksuite/affine/inlines/comment/package.json
Normal file
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
11
blocksuite/affine/inlines/comment/src/effects.ts
Normal file
11
blocksuite/affine/inlines/comment/src/effects.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { InlineComment } from './inline-comment';
|
||||
|
||||
export function effects() {
|
||||
customElements.define('inline-comment', InlineComment);
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'inline-comment': InlineComment;
|
||||
}
|
||||
}
|
||||
2
blocksuite/affine/inlines/comment/src/index.ts
Normal file
2
blocksuite/affine/inlines/comment/src/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './inline-spec';
|
||||
export * from './utils';
|
||||
175
blocksuite/affine/inlines/comment/src/inline-comment-manager.ts
Normal file
175
blocksuite/affine/inlines/comment/src/inline-comment-manager.ts
Normal file
@@ -0,0 +1,175 @@
|
||||
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);
|
||||
}
|
||||
};
|
||||
}
|
||||
95
blocksuite/affine/inlines/comment/src/inline-comment.ts
Normal file
95
blocksuite/affine/inlines/comment/src/inline-comment.ts
Normal file
@@ -0,0 +1,95 @@
|
||||
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>`;
|
||||
}
|
||||
}
|
||||
38
blocksuite/affine/inlines/comment/src/inline-spec.ts
Normal file
38
blocksuite/affine/inlines/comment/src/inline-spec.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
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,
|
||||
});
|
||||
53
blocksuite/affine/inlines/comment/src/utils.ts
Normal file
53
blocksuite/affine/inlines/comment/src/utils.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
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-', ''));
|
||||
}
|
||||
22
blocksuite/affine/inlines/comment/src/view.ts
Normal file
22
blocksuite/affine/inlines/comment/src/view.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
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]);
|
||||
}
|
||||
}
|
||||
18
blocksuite/affine/inlines/comment/tsconfig.json
Normal file
18
blocksuite/affine/inlines/comment/tsconfig.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"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,6 +3,7 @@ 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';
|
||||
|
||||
@@ -13,7 +14,9 @@ export const FootNoteInlineSpecExtension =
|
||||
provider.getOptional(FootNoteNodeConfigIdentifier) ?? undefined;
|
||||
return {
|
||||
name: 'footnote',
|
||||
schema: FootNoteSchema.optional().nullable().catch(undefined),
|
||||
schema: z.object({
|
||||
footnote: FootNoteSchema.optional().nullable().catch(undefined),
|
||||
}),
|
||||
match: delta => {
|
||||
return !!delta.attributes?.footnote;
|
||||
},
|
||||
|
||||
@@ -9,7 +9,9 @@ export const LatexInlineSpecExtension =
|
||||
const std = provider.get(StdIdentifier);
|
||||
return {
|
||||
name: 'latex',
|
||||
schema: z.string().optional().nullable().catch(undefined),
|
||||
schema: z.object({
|
||||
latex: z.string().optional().nullable().catch(undefined),
|
||||
}),
|
||||
match: delta => typeof delta.attributes?.latex === 'string',
|
||||
renderer: ({ delta, selected, editor, startOffset, endOffset }) => {
|
||||
return html`<affine-latex-node
|
||||
@@ -28,7 +30,9 @@ export const LatexInlineSpecExtension =
|
||||
export const LatexEditorUnitSpecExtension =
|
||||
InlineSpecExtension<AffineTextAttributes>({
|
||||
name: 'latex-editor-unit',
|
||||
schema: z.undefined(),
|
||||
schema: z.object({
|
||||
'latex-editor-unit': z.undefined(),
|
||||
}),
|
||||
match: () => true,
|
||||
renderer: ({ delta }) => {
|
||||
return html`<latex-editor-unit .delta=${delta}></latex-editor-unit>`;
|
||||
|
||||
@@ -9,7 +9,9 @@ export const LinkInlineSpecExtension =
|
||||
const std = provider.get(StdIdentifier);
|
||||
return {
|
||||
name: 'link',
|
||||
schema: z.string().optional().nullable().catch(undefined),
|
||||
schema: z.object({
|
||||
link: z.string().optional().nullable().catch(undefined),
|
||||
}),
|
||||
match: delta => {
|
||||
return !!delta.attributes?.link;
|
||||
},
|
||||
|
||||
@@ -23,7 +23,7 @@ export class AffineMention extends SignalWatcher(
|
||||
'clig' off;
|
||||
/* Client/baseMedium */
|
||||
font-family: Inter;
|
||||
font-size: 15px;
|
||||
font-size: var(--affine-font-size-base);
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
line-height: 24px; /* 160% */
|
||||
|
||||
@@ -9,14 +9,16 @@ export const MentionInlineSpecExtension =
|
||||
const std = provider.get(StdIdentifier);
|
||||
return {
|
||||
name: 'mention',
|
||||
schema: z
|
||||
.object({
|
||||
member: z.string(),
|
||||
notification: z.string().optional(),
|
||||
})
|
||||
.optional()
|
||||
.nullable()
|
||||
.catch(undefined),
|
||||
schema: z.object({
|
||||
mention: z
|
||||
.object({
|
||||
member: z.string(),
|
||||
notification: z.string().optional(),
|
||||
})
|
||||
.optional()
|
||||
.nullable()
|
||||
.catch(undefined),
|
||||
}),
|
||||
match: delta => {
|
||||
return !!delta.attributes?.mention?.member;
|
||||
},
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
"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 {
|
||||
isTextStyleActive,
|
||||
isTextAttributeActive,
|
||||
toggleBold,
|
||||
toggleCode,
|
||||
toggleItalic,
|
||||
@@ -38,7 +38,7 @@ export const textFormatConfigs: TextFormatConfig[] = [
|
||||
activeWhen: host => {
|
||||
const [result] = host.std.command
|
||||
.chain()
|
||||
.pipe(isTextStyleActive, { key: 'bold' })
|
||||
.pipe(isTextAttributeActive, { key: 'bold' })
|
||||
.run();
|
||||
return result;
|
||||
},
|
||||
@@ -54,7 +54,7 @@ export const textFormatConfigs: TextFormatConfig[] = [
|
||||
activeWhen: host => {
|
||||
const [result] = host.std.command
|
||||
.chain()
|
||||
.pipe(isTextStyleActive, { key: 'italic' })
|
||||
.pipe(isTextAttributeActive, { key: 'italic' })
|
||||
.run();
|
||||
return result;
|
||||
},
|
||||
@@ -70,7 +70,7 @@ export const textFormatConfigs: TextFormatConfig[] = [
|
||||
activeWhen: host => {
|
||||
const [result] = host.std.command
|
||||
.chain()
|
||||
.pipe(isTextStyleActive, { key: 'underline' })
|
||||
.pipe(isTextAttributeActive, { key: 'underline' })
|
||||
.run();
|
||||
return result;
|
||||
},
|
||||
@@ -86,7 +86,7 @@ export const textFormatConfigs: TextFormatConfig[] = [
|
||||
activeWhen: host => {
|
||||
const [result] = host.std.command
|
||||
.chain()
|
||||
.pipe(isTextStyleActive, { key: 'strike' })
|
||||
.pipe(isTextAttributeActive, { key: 'strike' })
|
||||
.run();
|
||||
return result;
|
||||
},
|
||||
@@ -102,7 +102,7 @@ export const textFormatConfigs: TextFormatConfig[] = [
|
||||
activeWhen: host => {
|
||||
const [result] = host.std.command
|
||||
.chain()
|
||||
.pipe(isTextStyleActive, { key: 'code' })
|
||||
.pipe(isTextAttributeActive, { key: 'code' })
|
||||
.run();
|
||||
return result;
|
||||
},
|
||||
@@ -118,7 +118,7 @@ export const textFormatConfigs: TextFormatConfig[] = [
|
||||
activeWhen: host => {
|
||||
const [result] = host.std.command
|
||||
.chain()
|
||||
.pipe(isTextStyleActive, { key: 'link' })
|
||||
.pipe(isTextAttributeActive, { key: 'link' })
|
||||
.run();
|
||||
return result;
|
||||
},
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { clearMarksOnDiscontinuousInput } from '@blocksuite/affine-rich-text';
|
||||
import { getSelectedBlocksCommand } from '@blocksuite/affine-shared/commands';
|
||||
import type { AffineTextAttributes } from '@blocksuite/affine-shared/types';
|
||||
import type {
|
||||
AffineTextAttributes,
|
||||
AffineTextStyleAttributes,
|
||||
} from '@blocksuite/affine-shared/types';
|
||||
import type { Command, TextSelection } from '@blocksuite/std';
|
||||
import {
|
||||
INLINE_ROOT_ATTR,
|
||||
@@ -13,7 +16,7 @@ import { FORMAT_TEXT_SUPPORT_FLAVOURS } from './consts.js';
|
||||
export const formatTextCommand: Command<{
|
||||
currentTextSelection?: TextSelection;
|
||||
textSelection?: TextSelection;
|
||||
styles: AffineTextAttributes;
|
||||
styles: AffineTextStyleAttributes;
|
||||
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 {
|
||||
getTextStyle,
|
||||
isTextStyleActive,
|
||||
getTextAttributes,
|
||||
isTextAttributeActive,
|
||||
toggleBold,
|
||||
toggleCode,
|
||||
toggleItalic,
|
||||
|
||||
@@ -2,25 +2,31 @@ import {
|
||||
getBlockSelectionsCommand,
|
||||
getTextSelectionCommand,
|
||||
} from '@blocksuite/affine-shared/commands';
|
||||
import type { AffineTextAttributes } from '@blocksuite/affine-shared/types';
|
||||
import type {
|
||||
AffineTextAttributes,
|
||||
AffineTextStyleAttributes,
|
||||
} 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 { getCombinedTextStyle } from './utils.js';
|
||||
import { getCombinedTextAttributes } from './utils.js';
|
||||
|
||||
export const toggleTextStyleCommand: Command<{
|
||||
key: Extract<
|
||||
keyof AffineTextAttributes,
|
||||
keyof AffineTextStyleAttributes,
|
||||
'bold' | 'italic' | 'underline' | 'strike' | 'code'
|
||||
>;
|
||||
}> = (ctx, next) => {
|
||||
const { std, key } = ctx;
|
||||
const [active] = std.command.chain().pipe(isTextStyleActive, { key }).run();
|
||||
const [active] = std.command
|
||||
.chain()
|
||||
.pipe(isTextAttributeActive, { key })
|
||||
.run();
|
||||
|
||||
const payload: {
|
||||
styles: AffineTextAttributes;
|
||||
styles: AffineTextStyleAttributes;
|
||||
mode?: 'replace' | 'merge';
|
||||
} = {
|
||||
styles: {
|
||||
@@ -46,7 +52,7 @@ export const toggleTextStyleCommand: Command<{
|
||||
|
||||
const toggleTextStyleCommandWrapper = (
|
||||
key: Extract<
|
||||
keyof AffineTextAttributes,
|
||||
keyof AffineTextStyleAttributes,
|
||||
'bold' | 'italic' | 'underline' | 'strike' | 'code'
|
||||
>
|
||||
): Command => {
|
||||
@@ -66,30 +72,29 @@ export const toggleUnderline = toggleTextStyleCommandWrapper('underline');
|
||||
export const toggleStrike = toggleTextStyleCommandWrapper('strike');
|
||||
export const toggleCode = toggleTextStyleCommandWrapper('code');
|
||||
|
||||
export const getTextStyle: Command<{}, { textStyle: AffineTextAttributes }> = (
|
||||
ctx,
|
||||
next
|
||||
) => {
|
||||
const [result, innerCtx] = getCombinedTextStyle(
|
||||
export const getTextAttributes: Command<
|
||||
{},
|
||||
{ textAttributes: AffineTextAttributes }
|
||||
> = (ctx, next) => {
|
||||
const [result, innerCtx] = getCombinedTextAttributes(
|
||||
ctx.std.command.chain()
|
||||
).run();
|
||||
if (!result) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return next({ textStyle: innerCtx.textStyle });
|
||||
return next({ textAttributes: innerCtx.textAttributes });
|
||||
};
|
||||
|
||||
export const isTextStyleActive: Command<{ key: keyof AffineTextAttributes }> = (
|
||||
ctx,
|
||||
next
|
||||
) => {
|
||||
export const isTextAttributeActive: Command<{
|
||||
key: keyof AffineTextAttributes;
|
||||
}> = (ctx, next) => {
|
||||
const key = ctx.key;
|
||||
const [result] = getCombinedTextStyle(ctx.std.command.chain())
|
||||
const [result] = getCombinedTextAttributes(ctx.std.command.chain())
|
||||
.pipe((ctx, next) => {
|
||||
const { textStyle } = ctx;
|
||||
const { textAttributes } = ctx;
|
||||
|
||||
if (textStyle && key in textStyle) {
|
||||
if (textAttributes && key in textAttributes) {
|
||||
return next();
|
||||
}
|
||||
|
||||
|
||||
@@ -77,8 +77,8 @@ function handleCurrentSelection(
|
||||
handler: (
|
||||
type: 'text' | 'block' | 'native',
|
||||
inlineEditors: InlineEditor<AffineTextAttributes>[]
|
||||
) => { textStyle: AffineTextAttributes } | boolean | void
|
||||
): Chain<InitCommandCtx & { textStyle: AffineTextAttributes }> {
|
||||
) => { textAttributes: AffineTextAttributes } | boolean | void
|
||||
): Chain<InitCommandCtx & { textAttributes: AffineTextAttributes }> {
|
||||
return chain.try(chain => [
|
||||
// text selection, corresponding to `formatText` command
|
||||
chain
|
||||
@@ -174,25 +174,25 @@ function handleCurrentSelection(
|
||||
]);
|
||||
}
|
||||
|
||||
export function getCombinedTextStyle(chain: Chain<InitCommandCtx>) {
|
||||
export function getCombinedTextAttributes(chain: Chain<InitCommandCtx>) {
|
||||
return handleCurrentSelection(chain, (type, inlineEditors) => {
|
||||
if (type === 'text') {
|
||||
return {
|
||||
textStyle: getCombinedFormatFromInlineEditors(
|
||||
textAttributes: getCombinedFormatFromInlineEditors(
|
||||
inlineEditors.map(e => [e, e.getInlineRange()])
|
||||
),
|
||||
};
|
||||
}
|
||||
if (type === 'block') {
|
||||
return {
|
||||
textStyle: getCombinedFormatFromInlineEditors(
|
||||
textAttributes: getCombinedFormatFromInlineEditors(
|
||||
inlineEditors.map(e => [e, { index: 0, length: e.yTextLength }])
|
||||
),
|
||||
};
|
||||
}
|
||||
if (type === 'native') {
|
||||
return {
|
||||
textStyle: getCombinedFormatFromInlineEditors(
|
||||
textAttributes: getCombinedFormatFromInlineEditors(
|
||||
inlineEditors.map(e => [e, e.getInlineRange()])
|
||||
),
|
||||
};
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
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';
|
||||
@@ -32,5 +33,6 @@ export const DefaultInlineManagerExtension =
|
||||
LinkInlineSpecExtension.identifier,
|
||||
FootNoteInlineSpecExtension.identifier,
|
||||
MentionInlineSpecExtension.identifier,
|
||||
CommentInlineSpecExtension.identifier,
|
||||
],
|
||||
});
|
||||
|
||||
@@ -12,7 +12,9 @@ export type AffineInlineRootElement = InlineRootElement<AffineTextAttributes>;
|
||||
export const BoldInlineSpecExtension =
|
||||
InlineSpecExtension<AffineTextAttributes>({
|
||||
name: 'bold',
|
||||
schema: z.literal(true).optional().nullable().catch(undefined),
|
||||
schema: z.object({
|
||||
bold: z.literal(true).optional().nullable().catch(undefined),
|
||||
}),
|
||||
match: delta => {
|
||||
return !!delta.attributes?.bold;
|
||||
},
|
||||
@@ -24,7 +26,9 @@ export const BoldInlineSpecExtension =
|
||||
export const ItalicInlineSpecExtension =
|
||||
InlineSpecExtension<AffineTextAttributes>({
|
||||
name: 'italic',
|
||||
schema: z.literal(true).optional().nullable().catch(undefined),
|
||||
schema: z.object({
|
||||
italic: z.literal(true).optional().nullable().catch(undefined),
|
||||
}),
|
||||
match: delta => {
|
||||
return !!delta.attributes?.italic;
|
||||
},
|
||||
@@ -36,7 +40,9 @@ export const ItalicInlineSpecExtension =
|
||||
export const UnderlineInlineSpecExtension =
|
||||
InlineSpecExtension<AffineTextAttributes>({
|
||||
name: 'underline',
|
||||
schema: z.literal(true).optional().nullable().catch(undefined),
|
||||
schema: z.object({
|
||||
underline: z.literal(true).optional().nullable().catch(undefined),
|
||||
}),
|
||||
match: delta => {
|
||||
return !!delta.attributes?.underline;
|
||||
},
|
||||
@@ -48,7 +54,9 @@ export const UnderlineInlineSpecExtension =
|
||||
export const StrikeInlineSpecExtension =
|
||||
InlineSpecExtension<AffineTextAttributes>({
|
||||
name: 'strike',
|
||||
schema: z.literal(true).optional().nullable().catch(undefined),
|
||||
schema: z.object({
|
||||
strike: z.literal(true).optional().nullable().catch(undefined),
|
||||
}),
|
||||
match: delta => {
|
||||
return !!delta.attributes?.strike;
|
||||
},
|
||||
@@ -60,7 +68,9 @@ export const StrikeInlineSpecExtension =
|
||||
export const CodeInlineSpecExtension =
|
||||
InlineSpecExtension<AffineTextAttributes>({
|
||||
name: 'inline-code',
|
||||
schema: z.literal(true).optional().nullable().catch(undefined),
|
||||
schema: z.object({
|
||||
code: z.literal(true).optional().nullable().catch(undefined),
|
||||
}),
|
||||
match: delta => {
|
||||
return !!delta.attributes?.code;
|
||||
},
|
||||
@@ -72,7 +82,9 @@ export const CodeInlineSpecExtension =
|
||||
export const BackgroundInlineSpecExtension =
|
||||
InlineSpecExtension<AffineTextAttributes>({
|
||||
name: 'background',
|
||||
schema: z.string().optional().nullable().catch(undefined),
|
||||
schema: z.object({
|
||||
background: z.string().optional().nullable().catch(undefined),
|
||||
}),
|
||||
match: delta => {
|
||||
return !!delta.attributes?.background;
|
||||
},
|
||||
@@ -84,7 +96,9 @@ export const BackgroundInlineSpecExtension =
|
||||
export const ColorInlineSpecExtension =
|
||||
InlineSpecExtension<AffineTextAttributes>({
|
||||
name: 'color',
|
||||
schema: z.string().optional().nullable().catch(undefined),
|
||||
schema: z.object({
|
||||
color: z.string().optional().nullable().catch(undefined),
|
||||
}),
|
||||
match: delta => {
|
||||
return !!delta.attributes?.color;
|
||||
},
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
"references": [
|
||||
{ "path": "../../components" },
|
||||
{ "path": "../../ext-loader" },
|
||||
{ "path": "../comment" },
|
||||
{ "path": "../footnote" },
|
||||
{ "path": "../latex" },
|
||||
{ "path": "../link" },
|
||||
|
||||
@@ -27,18 +27,20 @@ export const ReferenceInlineSpecExtension =
|
||||
}
|
||||
return {
|
||||
name: 'reference',
|
||||
schema: z
|
||||
.object({
|
||||
type: z.enum([
|
||||
// @deprecated Subpage is deprecated, use LinkedPage instead
|
||||
'Subpage',
|
||||
'LinkedPage',
|
||||
]),
|
||||
})
|
||||
.merge(ReferenceInfoSchema)
|
||||
.optional()
|
||||
.nullable()
|
||||
.catch(undefined),
|
||||
schema: z.object({
|
||||
reference: 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,6 +58,8 @@ export type AttachmentBlockProps = {
|
||||
style?: (typeof AttachmentBlockStyles)[number];
|
||||
|
||||
footnoteIdentifier: string | null;
|
||||
|
||||
comments?: Record<string, boolean>;
|
||||
} & Omit<GfxCommonBlockProps, 'scale'> &
|
||||
BlockMeta;
|
||||
|
||||
@@ -78,6 +80,7 @@ export const defaultAttachmentProps: AttachmentBlockProps = {
|
||||
'meta:createdBy': undefined,
|
||||
'meta:updatedBy': undefined,
|
||||
footnoteIdentifier: null,
|
||||
comments: undefined,
|
||||
};
|
||||
|
||||
export const AttachmentBlockSchema = defineBlockSchema({
|
||||
|
||||
@@ -28,6 +28,7 @@ export type BookmarkBlockProps = {
|
||||
url: string;
|
||||
caption: string | null;
|
||||
footnoteIdentifier: string | null;
|
||||
comments?: Record<string, boolean>;
|
||||
} & LinkPreviewData &
|
||||
Omit<GfxCommonBlockProps, 'scale'> &
|
||||
BlockMeta;
|
||||
@@ -52,6 +53,7 @@ const defaultBookmarkProps: BookmarkBlockProps = {
|
||||
'meta:updatedBy': undefined,
|
||||
|
||||
footnoteIdentifier: null,
|
||||
comments: undefined,
|
||||
};
|
||||
|
||||
export const BookmarkBlockSchema = defineBlockSchema({
|
||||
|
||||
@@ -14,6 +14,7 @@ type CodeBlockProps = {
|
||||
caption: string;
|
||||
preview?: boolean;
|
||||
lineNumber?: boolean;
|
||||
comments?: Record<string, boolean>;
|
||||
} & BlockMeta;
|
||||
|
||||
export const CodeBlockSchema = defineBlockSchema({
|
||||
@@ -26,6 +27,7 @@ export const CodeBlockSchema = defineBlockSchema({
|
||||
caption: '',
|
||||
preview: undefined,
|
||||
lineNumber: undefined,
|
||||
comments: undefined,
|
||||
'meta:createdAt': undefined,
|
||||
'meta:createdBy': undefined,
|
||||
'meta:updatedAt': undefined,
|
||||
|
||||
@@ -16,6 +16,7 @@ export type DatabaseBlockProps = {
|
||||
title: Text;
|
||||
cells: SerializedCells;
|
||||
columns: Array<ColumnDataType>;
|
||||
comments?: Record<string, boolean>;
|
||||
};
|
||||
|
||||
export class DatabaseBlockModel extends BlockModel<DatabaseBlockProps> {}
|
||||
@@ -27,6 +28,7 @@ export const DatabaseBlockSchema = defineBlockSchema({
|
||||
title: internal.Text(),
|
||||
cells: Object.create(null),
|
||||
columns: [],
|
||||
comments: undefined,
|
||||
}),
|
||||
metadata: {
|
||||
role: 'hub',
|
||||
|
||||
@@ -26,6 +26,7 @@ import { DefaultTheme } from '../../themes/default';
|
||||
|
||||
type EdgelessTextProps = {
|
||||
hasMaxWidth: boolean;
|
||||
comments?: Record<string, boolean>;
|
||||
} & Omit<TextStyleProps, 'fontSize'> &
|
||||
GfxCommonBlockProps;
|
||||
|
||||
@@ -54,6 +55,7 @@ export const EdgelessTextBlockSchema = defineBlockSchema({
|
||||
scale: 1,
|
||||
rotate: 0,
|
||||
hasMaxWidth: false,
|
||||
comments: undefined,
|
||||
...EdgelessTextZodSchema.parse(undefined),
|
||||
}),
|
||||
metadata: {
|
||||
|
||||
@@ -30,6 +30,7 @@ export type FrameBlockProps = {
|
||||
background: Color;
|
||||
childElementIds?: Record<string, boolean>;
|
||||
presentationIndex?: string;
|
||||
comments?: Record<string, boolean>;
|
||||
} & GfxCompatibleProps;
|
||||
|
||||
export const FrameZodSchema = z
|
||||
@@ -50,6 +51,7 @@ export const FrameBlockSchema = defineBlockSchema({
|
||||
childElementIds: Object.create(null),
|
||||
presentationIndex: generateKeyBetweenV2(null, null),
|
||||
lockedBySelf: false,
|
||||
comments: undefined,
|
||||
}),
|
||||
metadata: {
|
||||
version: 1,
|
||||
|
||||
@@ -19,6 +19,7 @@ export type ImageBlockProps = {
|
||||
height?: number;
|
||||
rotate: number;
|
||||
size?: number;
|
||||
comments?: Record<string, boolean>;
|
||||
} & Omit<GfxCommonBlockProps, 'scale'> &
|
||||
BlockMeta;
|
||||
|
||||
@@ -32,6 +33,7 @@ const defaultImageProps: ImageBlockProps = {
|
||||
lockedBySelf: false,
|
||||
rotate: 0,
|
||||
size: -1,
|
||||
comments: undefined,
|
||||
'meta:createdAt': undefined,
|
||||
'meta:createdBy': undefined,
|
||||
'meta:updatedAt': undefined,
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
|
||||
export type LatexProps = {
|
||||
latex: string;
|
||||
comments?: Record<string, boolean>;
|
||||
} & GfxCommonBlockProps;
|
||||
|
||||
export const LatexBlockSchema = defineBlockSchema({
|
||||
@@ -22,6 +23,7 @@ export const LatexBlockSchema = defineBlockSchema({
|
||||
scale: 1,
|
||||
rotate: 0,
|
||||
latex: '',
|
||||
comments: undefined,
|
||||
}),
|
||||
metadata: {
|
||||
version: 1,
|
||||
|
||||
@@ -16,6 +16,7 @@ export type ListProps = {
|
||||
checked: boolean;
|
||||
collapsed: boolean;
|
||||
order: number | null;
|
||||
comments?: Record<string, boolean>;
|
||||
} & BlockMeta;
|
||||
|
||||
export const ListBlockSchema = defineBlockSchema({
|
||||
@@ -29,6 +30,7 @@ export const ListBlockSchema = defineBlockSchema({
|
||||
|
||||
// number type only for numbered list
|
||||
order: null,
|
||||
comments: undefined,
|
||||
'meta:createdAt': undefined,
|
||||
'meta:createdBy': undefined,
|
||||
'meta:updatedAt': undefined,
|
||||
|
||||
@@ -69,6 +69,7 @@ export const NoteBlockSchema = defineBlockSchema({
|
||||
shadowType: DEFAULT_NOTE_SHADOW,
|
||||
},
|
||||
},
|
||||
comments: undefined,
|
||||
}),
|
||||
metadata: {
|
||||
version: 1,
|
||||
@@ -91,6 +92,7 @@ export type NoteProps = {
|
||||
background: Color;
|
||||
displayMode: NoteDisplayMode;
|
||||
edgeless: NoteEdgelessProps;
|
||||
comments?: Record<string, boolean>;
|
||||
/**
|
||||
* @deprecated
|
||||
* use `displayMode` instead
|
||||
|
||||
@@ -21,6 +21,7 @@ export type ParagraphProps = {
|
||||
type: ParagraphType;
|
||||
text: Text;
|
||||
collapsed: boolean;
|
||||
comments?: Record<string, boolean>;
|
||||
} & BlockMeta;
|
||||
|
||||
export const ParagraphBlockSchema = defineBlockSchema({
|
||||
@@ -29,6 +30,7 @@ export const ParagraphBlockSchema = defineBlockSchema({
|
||||
type: 'text',
|
||||
text: internal.Text(),
|
||||
collapsed: false,
|
||||
comments: undefined,
|
||||
'meta:createdAt': undefined,
|
||||
'meta:createdBy': undefined,
|
||||
'meta:updatedAt': undefined,
|
||||
|
||||
@@ -8,14 +8,16 @@ export type SurfaceRefProps = {
|
||||
reference: string;
|
||||
caption: string;
|
||||
refFlavour: string;
|
||||
comments?: Record<string, boolean>;
|
||||
};
|
||||
|
||||
export const SurfaceRefBlockSchema = defineBlockSchema({
|
||||
flavour: 'affine:surface-ref',
|
||||
props: () => ({
|
||||
props: (): SurfaceRefProps => ({
|
||||
reference: '',
|
||||
caption: '',
|
||||
refFlavour: '',
|
||||
comments: undefined,
|
||||
}),
|
||||
metadata: {
|
||||
version: 1,
|
||||
|
||||
@@ -29,6 +29,7 @@ export interface TableBlockProps extends BlockMeta {
|
||||
columns: Record<string, TableColumn>;
|
||||
// key = `${rowId}:${columnId}`
|
||||
cells: Record<string, TableCell>;
|
||||
comments?: Record<string, boolean>;
|
||||
}
|
||||
|
||||
export interface TableCellSerialized {
|
||||
@@ -51,6 +52,7 @@ export const TableBlockSchema = defineBlockSchema({
|
||||
rows: {},
|
||||
columns: {},
|
||||
cells: {},
|
||||
comments: undefined,
|
||||
'meta:createdAt': undefined,
|
||||
'meta:createdBy': undefined,
|
||||
'meta:updatedAt': undefined,
|
||||
|
||||
@@ -43,6 +43,7 @@ export const ReferenceParamsSchema = z
|
||||
databaseId: z.string().optional(),
|
||||
databaseRowId: z.string().optional(),
|
||||
xywh: SerializedXYWHSchema.optional(),
|
||||
commentId: z.string().optional(),
|
||||
})
|
||||
.partial();
|
||||
|
||||
|
||||
@@ -10,7 +10,11 @@ import {
|
||||
|
||||
import type { BlockMeta } from './types';
|
||||
|
||||
export type EmbedProps<Props = object> = Props & GfxCompatibleProps & BlockMeta;
|
||||
export type EmbedProps<Props = object> = Props &
|
||||
GfxCompatibleProps &
|
||||
BlockMeta & {
|
||||
comments?: Record<string, boolean>;
|
||||
};
|
||||
|
||||
export function defineEmbedModel<
|
||||
Props extends object,
|
||||
@@ -52,6 +56,7 @@ export function createEmbedBlockSchema<
|
||||
xywh: '[0,0,0,0]',
|
||||
lockedBySelf: false,
|
||||
rotate: 0,
|
||||
comments: undefined,
|
||||
'meta:createdAt': undefined,
|
||||
'meta:updatedAt': undefined,
|
||||
'meta:createdBy': undefined,
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
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;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
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);
|
||||
},
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,3 @@
|
||||
export * from './block-comment-manager';
|
||||
export * from './comment-provider';
|
||||
export * from './utils';
|
||||
@@ -0,0 +1,45 @@
|
||||
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,6 +21,7 @@ export interface BlockSuiteFlags {
|
||||
enable_table_virtual_scroll: boolean;
|
||||
enable_turbo_renderer: boolean;
|
||||
enable_dom_renderer: boolean;
|
||||
enable_comment: boolean;
|
||||
}
|
||||
|
||||
export class FeatureFlagService extends StoreExtension {
|
||||
@@ -46,6 +47,7 @@ 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,6 +1,7 @@
|
||||
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';
|
||||
|
||||
@@ -35,27 +35,31 @@ export type IndentContext = {
|
||||
type: 'indent' | 'dedent';
|
||||
};
|
||||
|
||||
export interface AffineTextAttributes {
|
||||
export type AffineTextStyleAttributes = {
|
||||
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>;
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ import {
|
||||
formatBlockCommand,
|
||||
formatNativeCommand,
|
||||
formatTextCommand,
|
||||
getTextStyle,
|
||||
getTextAttributes,
|
||||
toggleBold,
|
||||
toggleCode,
|
||||
toggleItalic,
|
||||
@@ -45,7 +45,7 @@ import {
|
||||
getTextSelectionCommand,
|
||||
} from '@blocksuite/affine-shared/commands';
|
||||
import { REFERENCE_NODE } from '@blocksuite/affine-shared/consts';
|
||||
import type { AffineTextAttributes } from '@blocksuite/affine-shared/types';
|
||||
import type { AffineTextStyleAttributes } from '@blocksuite/affine-shared/types';
|
||||
import {
|
||||
createDefaultDoc,
|
||||
openSingleFileWith,
|
||||
@@ -817,8 +817,8 @@ const textStyleToolItems: KeyboardToolbarItem[] = [
|
||||
name: 'Bold',
|
||||
icon: BoldIcon(),
|
||||
background: ({ std }) => {
|
||||
const [_, { textStyle }] = std.command.exec(getTextStyle);
|
||||
return textStyle?.bold ? '#00000012' : '';
|
||||
const [_, { textAttributes }] = std.command.exec(getTextAttributes);
|
||||
return textAttributes?.bold ? '#00000012' : '';
|
||||
},
|
||||
action: ({ std }) => {
|
||||
std.command.exec(toggleBold);
|
||||
@@ -828,8 +828,8 @@ const textStyleToolItems: KeyboardToolbarItem[] = [
|
||||
name: 'Italic',
|
||||
icon: ItalicIcon(),
|
||||
background: ({ std }) => {
|
||||
const [_, { textStyle }] = std.command.exec(getTextStyle);
|
||||
return textStyle?.italic ? '#00000012' : '';
|
||||
const [_, { textAttributes }] = std.command.exec(getTextAttributes);
|
||||
return textAttributes?.italic ? '#00000012' : '';
|
||||
},
|
||||
action: ({ std }) => {
|
||||
std.command.exec(toggleItalic);
|
||||
@@ -839,8 +839,8 @@ const textStyleToolItems: KeyboardToolbarItem[] = [
|
||||
name: 'UnderLine',
|
||||
icon: UnderLineIcon(),
|
||||
background: ({ std }) => {
|
||||
const [_, { textStyle }] = std.command.exec(getTextStyle);
|
||||
return textStyle?.underline ? '#00000012' : '';
|
||||
const [_, { textAttributes }] = std.command.exec(getTextAttributes);
|
||||
return textAttributes?.underline ? '#00000012' : '';
|
||||
},
|
||||
action: ({ std }) => {
|
||||
std.command.exec(toggleUnderline);
|
||||
@@ -850,8 +850,8 @@ const textStyleToolItems: KeyboardToolbarItem[] = [
|
||||
name: 'StrikeThrough',
|
||||
icon: StrikeThroughIcon(),
|
||||
background: ({ std }) => {
|
||||
const [_, { textStyle }] = std.command.exec(getTextStyle);
|
||||
return textStyle?.strike ? '#00000012' : '';
|
||||
const [_, { textAttributes }] = std.command.exec(getTextAttributes);
|
||||
return textAttributes?.strike ? '#00000012' : '';
|
||||
},
|
||||
action: ({ std }) => {
|
||||
std.command.exec(toggleStrike);
|
||||
@@ -861,8 +861,8 @@ const textStyleToolItems: KeyboardToolbarItem[] = [
|
||||
name: 'Code',
|
||||
icon: CodeIcon(),
|
||||
background: ({ std }) => {
|
||||
const [_, { textStyle }] = std.command.exec(getTextStyle);
|
||||
return textStyle?.code ? '#00000012' : '';
|
||||
const [_, { textAttributes }] = std.command.exec(getTextAttributes);
|
||||
return textAttributes?.code ? '#00000012' : '';
|
||||
},
|
||||
action: ({ std }) => {
|
||||
std.command.exec(toggleCode);
|
||||
@@ -872,8 +872,8 @@ const textStyleToolItems: KeyboardToolbarItem[] = [
|
||||
name: 'Link',
|
||||
icon: LinkIcon(),
|
||||
background: ({ std }) => {
|
||||
const [_, { textStyle }] = std.command.exec(getTextStyle);
|
||||
return textStyle?.link ? '#00000012' : '';
|
||||
const [_, { textAttributes }] = std.command.exec(getTextAttributes);
|
||||
return textAttributes?.link ? '#00000012' : '';
|
||||
},
|
||||
action: ({ std }) => {
|
||||
std.command.exec(toggleLink);
|
||||
@@ -883,9 +883,9 @@ const textStyleToolItems: KeyboardToolbarItem[] = [
|
||||
|
||||
const highlightToolPanel: KeyboardToolPanelConfig = {
|
||||
icon: ({ std }) => {
|
||||
const [_, { textStyle }] = std.command.exec(getTextStyle);
|
||||
if (textStyle?.color) {
|
||||
return HighLightDuotoneIcon(textStyle.color);
|
||||
const [_, { textAttributes }] = std.command.exec(getTextAttributes);
|
||||
if (textAttributes?.color) {
|
||||
return HighLightDuotoneIcon(textAttributes.color);
|
||||
} else {
|
||||
return HighLightDuotoneIcon(cssVarV2('icon/primary'));
|
||||
}
|
||||
@@ -916,7 +916,7 @@ const highlightToolPanel: KeyboardToolPanelConfig = {
|
||||
const payload = {
|
||||
styles: {
|
||||
color: cssVarV2(`text/highlight/fg/${color}`),
|
||||
} satisfies AffineTextAttributes,
|
||||
} satisfies AffineTextStyleAttributes,
|
||||
};
|
||||
std.command
|
||||
.chain()
|
||||
@@ -961,7 +961,7 @@ const highlightToolPanel: KeyboardToolPanelConfig = {
|
||||
const payload = {
|
||||
styles: {
|
||||
background: cssVarV2(`text/highlight/bg/${color}`),
|
||||
} satisfies AffineTextAttributes,
|
||||
} satisfies AffineTextStyleAttributes,
|
||||
};
|
||||
std.command
|
||||
.chain()
|
||||
|
||||
@@ -579,9 +579,11 @@ export class AffineToolbarWidget extends WidgetComponent {
|
||||
);
|
||||
|
||||
// Handles elements when resizing
|
||||
const edgelessSlots = std.get(EdgelessLegacySlotIdentifier);
|
||||
disposables.add(edgelessSlots.elementResizeStart.subscribe(dragStart));
|
||||
disposables.add(edgelessSlots.elementResizeEnd.subscribe(dragEnd));
|
||||
const edgelessSlots = std.getOptional(EdgelessLegacySlotIdentifier);
|
||||
if (edgelessSlots) {
|
||||
disposables.add(edgelessSlots.elementResizeStart.subscribe(dragStart));
|
||||
disposables.add(edgelessSlots.elementResizeEnd.subscribe(dragEnd));
|
||||
}
|
||||
|
||||
// Handles elements when hovering
|
||||
disposables.add(
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
type DeltaInsert,
|
||||
type ExtensionType,
|
||||
} from '@blocksuite/store';
|
||||
import { z, type ZodObject, type ZodTypeAny } from 'zod';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { StdIdentifier } from '../../identifier.js';
|
||||
import type { BlockStdScope } from '../../scope/index.js';
|
||||
@@ -32,30 +32,37 @@ export class InlineManager<TextAttributes extends BaseTextAttributes> {
|
||||
|
||||
const renderer: AttributeRenderer<TextAttributes> = props => {
|
||||
// Priority increases from front to back
|
||||
for (const spec of this.specs.toReversed()) {
|
||||
const specs = this.specs.toReversed();
|
||||
const wrapperSpecs = specs.filter(spec => spec.wrapper);
|
||||
const normalSpecs = specs.filter(spec => !spec.wrapper);
|
||||
|
||||
let result = defaultRenderer(props);
|
||||
|
||||
for (const spec of normalSpecs) {
|
||||
if (spec.match(props.delta)) {
|
||||
return spec.renderer(props);
|
||||
result = spec.renderer(props);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return defaultRenderer(props);
|
||||
|
||||
for (const spec of wrapperSpecs) {
|
||||
if (spec.match(props.delta)) {
|
||||
result = spec.renderer({
|
||||
...props,
|
||||
children: result,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
return renderer;
|
||||
};
|
||||
|
||||
getSchema = (): ZodObject<Record<keyof TextAttributes, ZodTypeAny>> => {
|
||||
const defaultSchema = baseTextAttributes as unknown as ZodObject<
|
||||
Record<keyof TextAttributes, ZodTypeAny>
|
||||
>;
|
||||
|
||||
const schema: ZodObject<Record<keyof TextAttributes, ZodTypeAny>> =
|
||||
this.specs.reduce((acc, cur) => {
|
||||
const currentSchema = z.object({
|
||||
[cur.name]: cur.schema,
|
||||
}) as ZodObject<Record<keyof TextAttributes, ZodTypeAny>>;
|
||||
return acc.merge(currentSchema) as ZodObject<
|
||||
Record<keyof TextAttributes, ZodTypeAny>
|
||||
>;
|
||||
}, defaultSchema);
|
||||
getSchema = (): z.ZodSchema => {
|
||||
const schema = this.specs.reduce<z.ZodSchema>((acc, cur) => {
|
||||
return z.intersection(acc, cur.schema);
|
||||
}, baseTextAttributes);
|
||||
return schema;
|
||||
};
|
||||
|
||||
|
||||
@@ -5,16 +5,23 @@ import type {
|
||||
} from '@blocksuite/std/inline';
|
||||
import type { BaseTextAttributes, DeltaInsert } from '@blocksuite/store';
|
||||
import type * as Y from 'yjs';
|
||||
import type { ZodTypeAny } from 'zod';
|
||||
import type { AnyZodObject, KeySchema, ZodEffects, ZodRecord } from 'zod';
|
||||
|
||||
export type InlineSpecs<
|
||||
TextAttributes extends BaseTextAttributes = BaseTextAttributes,
|
||||
> = {
|
||||
name: keyof TextAttributes | string;
|
||||
schema: ZodTypeAny;
|
||||
schema:
|
||||
| AnyZodObject
|
||||
| ZodEffects<
|
||||
ZodRecord<KeySchema>,
|
||||
Partial<Record<string, unknown>>,
|
||||
unknown
|
||||
>;
|
||||
match: (delta: DeltaInsert<TextAttributes>) => boolean;
|
||||
renderer: AttributeRenderer<TextAttributes>;
|
||||
embed?: boolean;
|
||||
wrapper?: boolean;
|
||||
};
|
||||
|
||||
export type InlineMarkdownMatchAction<
|
||||
|
||||
@@ -279,7 +279,10 @@ export class InlineEditor<
|
||||
this._isReadonly = isReadonly;
|
||||
}
|
||||
|
||||
transact(fn: () => void): void {
|
||||
/**
|
||||
* @param withoutTransact Execute a transaction without capturing the history.
|
||||
*/
|
||||
transact(fn: () => void, withoutTransact = false): void {
|
||||
const doc = this.yText.doc;
|
||||
if (!doc) {
|
||||
throw new BlockSuiteError(
|
||||
@@ -288,6 +291,6 @@ export class InlineEditor<
|
||||
);
|
||||
}
|
||||
|
||||
doc.transact(fn, doc.clientID);
|
||||
doc.transact(fn, withoutTransact ? null : doc.clientID);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,11 +19,16 @@ export class InlineTextService<TextAttributes extends BaseTextAttributes> {
|
||||
options: {
|
||||
match?: (delta: DeltaInsert, deltaInlineRange: InlineRange) => boolean;
|
||||
mode?: 'replace' | 'merge';
|
||||
withoutTransact?: boolean;
|
||||
} = {}
|
||||
): void => {
|
||||
if (this.editor.isReadonly) return;
|
||||
|
||||
const { match = () => true, mode = 'merge' } = options;
|
||||
const {
|
||||
match = () => true,
|
||||
mode = 'merge',
|
||||
withoutTransact = false,
|
||||
} = options;
|
||||
const deltas = this.editor.deltaService.getDeltasByInlineRange(inlineRange);
|
||||
|
||||
deltas
|
||||
@@ -49,7 +54,7 @@ export class InlineTextService<TextAttributes extends BaseTextAttributes> {
|
||||
targetInlineRange.length,
|
||||
normalizedAttributes
|
||||
);
|
||||
});
|
||||
}, withoutTransact);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ export type AttributeRenderer<
|
||||
startOffset: number;
|
||||
endOffset: number;
|
||||
lineIndex: number;
|
||||
children?: TemplateResult<1>;
|
||||
}) => TemplateResult<1>;
|
||||
|
||||
export interface InlineRange {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user