mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-22 20:46:38 +08:00
chore: bump bs with new extension api (#8042)
This commit is contained in:
@@ -9,16 +9,12 @@ import { EditorService } from '@affine/core/modules/editor';
|
||||
import { WorkspacePermissionService } from '@affine/core/modules/permissions';
|
||||
import { WorkspaceQuotaService } from '@affine/core/modules/quota';
|
||||
import { i18nTime, Trans, useI18n } from '@affine/i18n';
|
||||
import type { DocMode } from '@blocksuite/blocks';
|
||||
import { CloseIcon, ToggleCollapseIcon } from '@blocksuite/icons/rc';
|
||||
import type { Doc as BlockSuiteDoc, DocCollection } from '@blocksuite/store';
|
||||
import * as Collapsible from '@radix-ui/react-collapsible';
|
||||
import type { DialogContentProps } from '@radix-ui/react-dialog';
|
||||
import {
|
||||
type DocMode,
|
||||
useLiveData,
|
||||
useService,
|
||||
WorkspaceService,
|
||||
} from '@toeverything/infra';
|
||||
import { useLiveData, useService, WorkspaceService } from '@toeverything/infra';
|
||||
import { atom, useAtom, useSetAtom } from 'jotai';
|
||||
import type { PropsWithChildren } from 'react';
|
||||
import {
|
||||
|
||||
@@ -6,23 +6,27 @@ import {
|
||||
} from '@affine/core/modules/peek-view';
|
||||
import { WorkbenchLink } from '@affine/core/modules/workbench';
|
||||
import { useI18n } from '@affine/i18n';
|
||||
import type { BlockStdScope } from '@blocksuite/block-std';
|
||||
import { DocMode } from '@blocksuite/blocks';
|
||||
import {
|
||||
BlockLinkIcon,
|
||||
DeleteIcon,
|
||||
LinkedEdgelessIcon,
|
||||
LinkedPageIcon,
|
||||
TodayIcon,
|
||||
} from '@blocksuite/icons/rc';
|
||||
import type { DocCollection } from '@blocksuite/store';
|
||||
import { useService } from '@toeverything/infra';
|
||||
import {
|
||||
type DocMode,
|
||||
DocsService,
|
||||
LiveData,
|
||||
useLiveData,
|
||||
useService,
|
||||
} from '@toeverything/infra';
|
||||
import { type PropsWithChildren, useCallback, useRef } from 'react';
|
||||
type PropsWithChildren,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
|
||||
import * as styles from './styles.css';
|
||||
import { scrollAnchoring } from './utils';
|
||||
|
||||
export interface PageReferenceRendererOptions {
|
||||
docMode: DocMode | null;
|
||||
@@ -31,6 +35,9 @@ export interface PageReferenceRendererOptions {
|
||||
pageMetaHelper: ReturnType<typeof useDocMetaHelper>;
|
||||
journalHelper: ReturnType<typeof useJournalHelper>;
|
||||
t: ReturnType<typeof useI18n>;
|
||||
// linking doc with block or element
|
||||
blockIds?: string[];
|
||||
elementIds?: string[];
|
||||
}
|
||||
// use a function to be rendered in the lit renderer
|
||||
export function pageReferenceRenderer({
|
||||
@@ -39,30 +46,37 @@ export function pageReferenceRenderer({
|
||||
pageMetaHelper,
|
||||
journalHelper,
|
||||
t,
|
||||
blockIds,
|
||||
elementIds,
|
||||
}: PageReferenceRendererOptions) {
|
||||
const { isPageJournal, getLocalizedJournalDateString } = journalHelper;
|
||||
const referencedPage = pageMetaHelper.getDocMeta(pageId);
|
||||
let title =
|
||||
referencedPage?.title ?? t['com.affine.editor.reference-not-found']();
|
||||
|
||||
let icon = !referencedPage ? (
|
||||
<DeleteIcon className={styles.pageReferenceIcon} />
|
||||
) : docMode === 'page' || docMode === null ? (
|
||||
<LinkedPageIcon className={styles.pageReferenceIcon} />
|
||||
) : (
|
||||
<LinkedEdgelessIcon className={styles.pageReferenceIcon} />
|
||||
);
|
||||
let Icon = DeleteIcon;
|
||||
|
||||
if (referencedPage) {
|
||||
if (docMode === DocMode.Edgeless) {
|
||||
Icon = LinkedEdgelessIcon;
|
||||
} else {
|
||||
Icon = LinkedPageIcon;
|
||||
}
|
||||
if (blockIds?.length || elementIds?.length) {
|
||||
Icon = BlockLinkIcon;
|
||||
}
|
||||
}
|
||||
|
||||
const isJournal = isPageJournal(pageId);
|
||||
const localizedJournalDate = getLocalizedJournalDateString(pageId);
|
||||
if (isJournal && localizedJournalDate) {
|
||||
title = localizedJournalDate;
|
||||
icon = <TodayIcon className={styles.pageReferenceIcon} />;
|
||||
Icon = TodayIcon;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{icon}
|
||||
<Icon className={styles.pageReferenceIcon} />
|
||||
<span className="affine-reference-title">
|
||||
{title ? title : t['Untitled']()}
|
||||
</span>
|
||||
@@ -74,32 +88,42 @@ export function AffinePageReference({
|
||||
pageId,
|
||||
docCollection,
|
||||
wrapper: Wrapper,
|
||||
mode = DocMode.Page,
|
||||
params = {},
|
||||
isSameDoc = false,
|
||||
std,
|
||||
}: {
|
||||
docCollection: DocCollection;
|
||||
pageId: string;
|
||||
wrapper?: React.ComponentType<PropsWithChildren>;
|
||||
mode?: DocMode;
|
||||
params?: {
|
||||
mode?: DocMode;
|
||||
blockIds?: string[];
|
||||
elementIds?: string[];
|
||||
};
|
||||
isSameDoc?: boolean;
|
||||
std?: BlockStdScope;
|
||||
}) {
|
||||
const pageMetaHelper = useDocMetaHelper(docCollection);
|
||||
const journalHelper = useJournalHelper(docCollection);
|
||||
const t = useI18n();
|
||||
const [anchor, setAnchor] = useState<{
|
||||
mode: DocMode;
|
||||
id: string;
|
||||
} | null>(null);
|
||||
|
||||
const { mode: linkedWithMode, blockIds, elementIds } = params;
|
||||
|
||||
const docsService = useService(DocsService);
|
||||
const docPrimaryMode = useLiveData(
|
||||
LiveData.computed(get => {
|
||||
const primaryMode$ = get(docsService.list.doc$(pageId))?.primaryMode$;
|
||||
if (!primaryMode$) {
|
||||
return null;
|
||||
}
|
||||
return get(primaryMode$);
|
||||
})
|
||||
);
|
||||
const el = pageReferenceRenderer({
|
||||
docMode: docPrimaryMode,
|
||||
docMode: linkedWithMode ?? mode,
|
||||
pageId,
|
||||
pageMetaHelper,
|
||||
journalHelper,
|
||||
docCollection,
|
||||
t,
|
||||
blockIds,
|
||||
elementIds,
|
||||
});
|
||||
|
||||
const ref = useRef<HTMLAnchorElement>(null);
|
||||
@@ -107,6 +131,18 @@ export function AffinePageReference({
|
||||
const peekView = useService(PeekViewService).peekView;
|
||||
const isInPeekView = useInsidePeekView();
|
||||
|
||||
useEffect(() => {
|
||||
if (isSameDoc) {
|
||||
if (mode === DocMode.Edgeless && elementIds?.length) {
|
||||
setAnchor({ mode, id: elementIds[0] });
|
||||
} else if (blockIds?.length) {
|
||||
setAnchor({ mode, id: blockIds[0] });
|
||||
}
|
||||
} else {
|
||||
setAnchor(null);
|
||||
}
|
||||
}, [std, isSameDoc, mode, blockIds, elementIds]);
|
||||
|
||||
const onClick = useCallback(
|
||||
(e: React.MouseEvent) => {
|
||||
if (e.shiftKey && ref.current) {
|
||||
@@ -114,18 +150,39 @@ export function AffinePageReference({
|
||||
e.stopPropagation();
|
||||
peekView.open(ref.current).catch(console.error);
|
||||
}
|
||||
if (isInPeekView) {
|
||||
|
||||
if (std && anchor) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
const { mode, id } = anchor;
|
||||
scrollAnchoring(std, mode, id);
|
||||
} else if (isInPeekView) {
|
||||
peekView.close();
|
||||
}
|
||||
|
||||
return;
|
||||
},
|
||||
[isInPeekView, peekView]
|
||||
[isInPeekView, peekView, anchor, std]
|
||||
);
|
||||
|
||||
// A block/element reference link
|
||||
const search = new URLSearchParams();
|
||||
if (linkedWithMode) {
|
||||
search.set('mode', linkedWithMode);
|
||||
}
|
||||
if (blockIds?.length) {
|
||||
search.set('blockIds', blockIds.join(','));
|
||||
}
|
||||
if (elementIds?.length) {
|
||||
search.set('elementIds', elementIds.join(','));
|
||||
}
|
||||
|
||||
const query = search.size > 0 ? `?${search.toString()}` : '';
|
||||
|
||||
return (
|
||||
<WorkbenchLink
|
||||
ref={ref}
|
||||
to={`/${pageId}`}
|
||||
to={`/${pageId}${query}`}
|
||||
onClick={onClick}
|
||||
className={styles.pageReferenceLink}
|
||||
>
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
import type { BlockStdScope } from '@blocksuite/block-std';
|
||||
import {
|
||||
DocMode,
|
||||
type EdgelessRootService,
|
||||
type PageRootService,
|
||||
} from '@blocksuite/blocks';
|
||||
import { Bound, deserializeXYWH } from '@blocksuite/global/utils';
|
||||
|
||||
function scrollAnchoringInEdgelessMode(
|
||||
service: EdgelessRootService,
|
||||
id: string
|
||||
) {
|
||||
requestAnimationFrame(() => {
|
||||
let isNotInNote = true;
|
||||
let bounds: Bound | null = null;
|
||||
|
||||
const blockComponent = service.std.view.getBlock(id);
|
||||
|
||||
const parentComponent = blockComponent?.parentComponent;
|
||||
if (parentComponent && parentComponent.flavour === 'affine:note') {
|
||||
isNotInNote = false;
|
||||
|
||||
const selection = parentComponent.std.selection;
|
||||
if (!selection) return;
|
||||
|
||||
selection.set([
|
||||
selection.create('block', {
|
||||
blockId: id,
|
||||
}),
|
||||
]);
|
||||
|
||||
const { left: x, width: w } = parentComponent.getBoundingClientRect();
|
||||
const { top: y, height: h } = blockComponent.getBoundingClientRect();
|
||||
const coord = service.viewport.toModelCoordFromClientCoord([x, y]);
|
||||
bounds = new Bound(
|
||||
coord[0],
|
||||
coord[1],
|
||||
w / service.viewport.zoom,
|
||||
h / service.viewport.zoom
|
||||
);
|
||||
} else {
|
||||
const model = service.getElementById(id);
|
||||
if (!model) return;
|
||||
|
||||
bounds = Bound.fromXYWH(deserializeXYWH(model.xywh));
|
||||
}
|
||||
|
||||
if (!bounds) return;
|
||||
|
||||
if (isNotInNote) {
|
||||
service.selection.set({
|
||||
elements: [id],
|
||||
editing: false,
|
||||
});
|
||||
}
|
||||
|
||||
const { zoom, centerX, centerY } = service.getFitToScreenData(
|
||||
[20, 20, 20, 20],
|
||||
[bounds]
|
||||
);
|
||||
|
||||
service.viewport.setViewport(zoom, [centerX, centerY]);
|
||||
|
||||
// const surfaceComponent = service.std.view.getBlock(service.surface.id);
|
||||
// if (!surfaceComponent) return;
|
||||
// (surfaceComponent as SurfaceBlockComponent).refresh();
|
||||
|
||||
// TODO(@fundon): toolbar should be hidden
|
||||
});
|
||||
}
|
||||
|
||||
function scrollAnchoringInPageMode(service: PageRootService, id: string) {
|
||||
const blockComponent = service.std.view.getBlock(id);
|
||||
if (!blockComponent || !blockComponent.path.length) return;
|
||||
|
||||
blockComponent.scrollIntoView({
|
||||
behavior: 'instant',
|
||||
block: 'center',
|
||||
});
|
||||
|
||||
const selection = service.std.selection;
|
||||
if (!selection) return;
|
||||
|
||||
selection.set([
|
||||
selection.create('block', {
|
||||
blockId: id,
|
||||
}),
|
||||
]);
|
||||
|
||||
// TODO(@fundon): toolbar should be hidden
|
||||
}
|
||||
|
||||
// TODO(@fundon): it should be a command
|
||||
export function scrollAnchoring(std: BlockStdScope, mode: DocMode, id: string) {
|
||||
if (mode === DocMode.Edgeless) {
|
||||
const service = std.getService<EdgelessRootService>('affine:page');
|
||||
if (!service) return;
|
||||
|
||||
scrollAnchoringInEdgelessMode(service, id);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const service = std.getService<PageRootService>('affine:page');
|
||||
if (!service) return;
|
||||
|
||||
scrollAnchoringInPageMode(service, id);
|
||||
}
|
||||
+2
-1
@@ -23,8 +23,9 @@ import {
|
||||
SystemFontFamilyService,
|
||||
} from '@affine/core/modules/system-font-family';
|
||||
import { useI18n } from '@affine/i18n';
|
||||
import type { DocMode } from '@blocksuite/blocks';
|
||||
import { DoneIcon, SearchIcon } from '@blocksuite/icons/rc';
|
||||
import { type DocMode, useLiveData, useServices } from '@toeverything/infra';
|
||||
import { useLiveData, useServices } from '@toeverything/infra';
|
||||
import clsx from 'clsx';
|
||||
import {
|
||||
type ChangeEvent,
|
||||
|
||||
+3
-2
@@ -12,6 +12,7 @@ import { ShareInfoService } from '@affine/core/modules/share-doc';
|
||||
import { WorkspaceFlavour } from '@affine/env/workspace';
|
||||
import { PublicPageMode } from '@affine/graphql';
|
||||
import { useI18n } from '@affine/i18n';
|
||||
import type { DocMode } from '@blocksuite/blocks';
|
||||
import {
|
||||
BlockIcon,
|
||||
CollaborationIcon,
|
||||
@@ -158,10 +159,10 @@ export const AFFiNESharePage = (props: ShareMenuProps) => {
|
||||
});
|
||||
|
||||
const onCopyPageLink = useCallback(() => {
|
||||
onClickCopyLink('page');
|
||||
onClickCopyLink('page' as DocMode);
|
||||
}, [onClickCopyLink]);
|
||||
const onCopyEdgelessLink = useCallback(() => {
|
||||
onClickCopyLink('edgeless');
|
||||
onClickCopyLink('edgeless' as DocMode);
|
||||
}, [onClickCopyLink]);
|
||||
const onCopyBlockLink = useCallback(() => {
|
||||
// TODO(@JimmFly): handle frame
|
||||
|
||||
+29
-75
@@ -1,15 +1,13 @@
|
||||
import type { BlockComponent } from '@blocksuite/block-std';
|
||||
import type { ReferenceInfo } from '@blocksuite/affine-model';
|
||||
import { DocMode } from '@blocksuite/blocks';
|
||||
import type {
|
||||
AffineEditorContainer,
|
||||
EdgelessEditor,
|
||||
PageEditor,
|
||||
} from '@blocksuite/presets';
|
||||
import type { Doc } from '@blocksuite/store';
|
||||
import { Slot } from '@blocksuite/store';
|
||||
import { type DocMode } from '@toeverything/infra';
|
||||
import { type Doc, Slot } from '@blocksuite/store';
|
||||
import clsx from 'clsx';
|
||||
import type React from 'react';
|
||||
import type { RefObject } from 'react';
|
||||
import {
|
||||
forwardRef,
|
||||
useEffect,
|
||||
@@ -19,6 +17,7 @@ import {
|
||||
useState,
|
||||
} from 'react';
|
||||
|
||||
import { scrollAnchoring } from '../../affine/reference-link/utils';
|
||||
import { BlocksuiteDocEditor, BlocksuiteEdgelessEditor } from './lit-adaper';
|
||||
import * as styles from './styles.css';
|
||||
|
||||
@@ -43,7 +42,8 @@ interface BlocksuiteEditorContainerProps {
|
||||
shared?: boolean;
|
||||
className?: string;
|
||||
style?: React.CSSProperties;
|
||||
defaultSelectedBlockId?: string;
|
||||
blockIds?: string[];
|
||||
elementIds?: string[];
|
||||
}
|
||||
|
||||
// mimic the interface of the webcomponent and expose slots & host
|
||||
@@ -53,68 +53,36 @@ type BlocksuiteEditorContainerRef = Pick<
|
||||
> &
|
||||
HTMLDivElement;
|
||||
|
||||
function findBlockElementById(container: HTMLElement, blockId: string) {
|
||||
const element = container.querySelector(
|
||||
`[data-block-id="${blockId}"]`
|
||||
) as BlockComponent | null;
|
||||
return element;
|
||||
}
|
||||
|
||||
// a workaround for returning the webcomponent for the given block id
|
||||
// by iterating over the children of the rendered dom tree
|
||||
const useBlockElementById = (
|
||||
containerRef: RefObject<HTMLElement | null>,
|
||||
blockId: string | undefined,
|
||||
timeout = 1000
|
||||
) => {
|
||||
const [blockElement, setBlockElement] = useState<BlockComponent | null>(null);
|
||||
useEffect(() => {
|
||||
if (!blockId) {
|
||||
return;
|
||||
}
|
||||
let canceled = false;
|
||||
const start = Date.now();
|
||||
function run() {
|
||||
if (canceled || !containerRef.current || !blockId) {
|
||||
return;
|
||||
}
|
||||
const element = findBlockElementById(containerRef.current, blockId);
|
||||
if (element) {
|
||||
setBlockElement(element);
|
||||
} else if (Date.now() - start < timeout) {
|
||||
setTimeout(run, 100);
|
||||
}
|
||||
}
|
||||
run();
|
||||
return () => {
|
||||
canceled = true;
|
||||
};
|
||||
}, [blockId, containerRef, timeout]);
|
||||
return blockElement;
|
||||
};
|
||||
|
||||
export const BlocksuiteEditorContainer = forwardRef<
|
||||
AffineEditorContainer,
|
||||
BlocksuiteEditorContainerProps
|
||||
>(function AffineEditorContainer(
|
||||
{ page, mode, className, style, defaultSelectedBlockId, shared },
|
||||
{ page, mode, className, style, shared, blockIds, elementIds },
|
||||
ref
|
||||
) {
|
||||
const scrolledRef = useRef(false);
|
||||
const hashChangedRef = useRef(false);
|
||||
const rootRef = useRef<HTMLDivElement>(null);
|
||||
const docRef = useRef<PageEditor>(null);
|
||||
const edgelessRef = useRef<EdgelessEditor>(null);
|
||||
const [anchor, setAnchor] = useState<string | null>(null);
|
||||
|
||||
const slots: BlocksuiteEditorContainerRef['slots'] = useMemo(() => {
|
||||
return {
|
||||
docLinkClicked: new Slot(),
|
||||
docLinkClicked: new Slot<ReferenceInfo>(),
|
||||
editorModeSwitched: new Slot(),
|
||||
docUpdated: new Slot(),
|
||||
tagClicked: new Slot(),
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (mode === DocMode.Edgeless && elementIds?.length) {
|
||||
setAnchor(elementIds[0]);
|
||||
} else if (blockIds?.length) {
|
||||
setAnchor(blockIds[0]);
|
||||
}
|
||||
}, [blockIds, elementIds, mode]);
|
||||
|
||||
// forward the slot to the webcomponent
|
||||
useLayoutEffect(() => {
|
||||
requestAnimationFrame(() => {
|
||||
@@ -209,36 +177,22 @@ export const BlocksuiteEditorContainer = forwardRef<
|
||||
}
|
||||
}, [affineEditorContainerProxy, ref]);
|
||||
|
||||
const blockElement = useBlockElementById(rootRef, defaultSelectedBlockId);
|
||||
// `scrolledRef` should be updated if blockElement is changed
|
||||
useEffect(() => {
|
||||
scrolledRef.current = false;
|
||||
}, [anchor]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!anchor) return;
|
||||
|
||||
let canceled = false;
|
||||
const handleScrollToBlock = (blockElement: BlockComponent) => {
|
||||
if (!mode || !blockElement) {
|
||||
return;
|
||||
}
|
||||
blockElement.scrollIntoView({
|
||||
behavior: 'instant',
|
||||
block: 'center',
|
||||
});
|
||||
const selectManager = affineEditorContainerProxy.host?.selection;
|
||||
if (!blockElement.path.length || !selectManager) {
|
||||
return;
|
||||
}
|
||||
const newSelection = selectManager.create('block', {
|
||||
blockId: blockElement.blockId,
|
||||
});
|
||||
selectManager.set([newSelection]);
|
||||
};
|
||||
affineEditorContainerProxy.updateComplete
|
||||
.then(() => {
|
||||
if (
|
||||
blockElement &&
|
||||
!scrolledRef.current &&
|
||||
!canceled &&
|
||||
!hashChangedRef.current
|
||||
) {
|
||||
handleScrollToBlock(blockElement);
|
||||
if (!scrolledRef.current && !canceled) {
|
||||
const std = affineEditorContainerProxy.host?.std;
|
||||
if (std) {
|
||||
scrollAnchoring(std, mode, anchor);
|
||||
}
|
||||
scrolledRef.current = true;
|
||||
}
|
||||
})
|
||||
@@ -246,7 +200,7 @@ export const BlocksuiteEditorContainer = forwardRef<
|
||||
return () => {
|
||||
canceled = true;
|
||||
};
|
||||
}, [blockElement, affineEditorContainerProxy, mode]);
|
||||
}, [anchor, affineEditorContainerProxy, mode]);
|
||||
|
||||
return (
|
||||
<div
|
||||
|
||||
+8
-4
@@ -1,4 +1,5 @@
|
||||
import { EditorLoading } from '@affine/component/page-detail-skeleton';
|
||||
import type { DocMode } from '@blocksuite/blocks';
|
||||
import { assertExists } from '@blocksuite/global/utils';
|
||||
import type { AffineEditorContainer } from '@blocksuite/presets';
|
||||
import type { Doc } from '@blocksuite/store';
|
||||
@@ -22,13 +23,14 @@ export type ErrorBoundaryProps = {
|
||||
|
||||
export type EditorProps = {
|
||||
page: Doc;
|
||||
mode: 'page' | 'edgeless';
|
||||
mode: DocMode;
|
||||
shared?: boolean;
|
||||
defaultSelectedBlockId?: string;
|
||||
// on Editor instance instantiated
|
||||
onLoadEditor?: (editor: AffineEditorContainer) => () => void;
|
||||
style?: CSSProperties;
|
||||
className?: string;
|
||||
blockIds?: string[];
|
||||
elementIds?: string[];
|
||||
};
|
||||
|
||||
function usePageRoot(page: Doc) {
|
||||
@@ -59,10 +61,11 @@ const BlockSuiteEditorImpl = forwardRef<AffineEditorContainer, EditorProps>(
|
||||
mode,
|
||||
page,
|
||||
className,
|
||||
defaultSelectedBlockId,
|
||||
onLoadEditor,
|
||||
shared,
|
||||
style,
|
||||
blockIds,
|
||||
elementIds,
|
||||
},
|
||||
ref
|
||||
) {
|
||||
@@ -113,7 +116,8 @@ const BlockSuiteEditorImpl = forwardRef<AffineEditorContainer, EditorProps>(
|
||||
ref={onRefChange}
|
||||
className={className}
|
||||
style={style}
|
||||
defaultSelectedBlockId={defaultSelectedBlockId}
|
||||
blockIds={blockIds}
|
||||
elementIds={elementIds}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
+27
-15
@@ -7,7 +7,7 @@ import { useJournalInfoHelper } from '@affine/core/hooks/use-journal';
|
||||
import { EditorSettingService } from '@affine/core/modules/editor-settting';
|
||||
import { PeekViewService } from '@affine/core/modules/peek-view';
|
||||
import { WorkbenchService } from '@affine/core/modules/workbench';
|
||||
import type { DocMode } from '@blocksuite/blocks';
|
||||
import { DocMode } from '@blocksuite/blocks';
|
||||
import { DocTitle, EdgelessEditor, PageEditor } from '@blocksuite/presets';
|
||||
import type { Doc } from '@blocksuite/store';
|
||||
import {
|
||||
@@ -73,13 +73,26 @@ const usePatchSpecs = (page: Doc, shared: boolean, mode: DocMode) => {
|
||||
const framework = useFramework();
|
||||
const referenceRenderer: ReferenceReactRenderer = useMemo(() => {
|
||||
return function customReference(reference) {
|
||||
const pageId = reference.delta.attributes?.reference?.pageId;
|
||||
const data = reference.delta.attributes?.reference;
|
||||
if (!data) return <span />;
|
||||
|
||||
const pageId = data.pageId;
|
||||
if (!pageId) return <span />;
|
||||
|
||||
const isSameDoc = pageId === page.id;
|
||||
|
||||
return (
|
||||
<AffinePageReference docCollection={page.collection} pageId={pageId} />
|
||||
<AffinePageReference
|
||||
docCollection={page.collection}
|
||||
pageId={pageId}
|
||||
mode={mode}
|
||||
params={data.params}
|
||||
std={reference.std}
|
||||
isSameDoc={isSameDoc}
|
||||
/>
|
||||
);
|
||||
};
|
||||
}, [page.collection]);
|
||||
}, [mode, page.collection, page.id]);
|
||||
|
||||
const specs = useMemo(() => {
|
||||
return mode === 'edgeless'
|
||||
@@ -89,20 +102,19 @@ const usePatchSpecs = (page: Doc, shared: boolean, mode: DocMode) => {
|
||||
|
||||
const confirmModal = useConfirmModal();
|
||||
const patchedSpecs = useMemo(() => {
|
||||
let patched = patchReferenceRenderer(specs, reactToLit, referenceRenderer);
|
||||
patched = patchNotificationService(
|
||||
patchReferenceRenderer(patched, reactToLit, referenceRenderer),
|
||||
confirmModal
|
||||
let patched = specs.concat(
|
||||
patchReferenceRenderer(reactToLit, referenceRenderer)
|
||||
);
|
||||
patched = patchPeekViewService(patched, peekViewService);
|
||||
patched = patchEdgelessClipboard(patched);
|
||||
patched = patched.concat(patchNotificationService(confirmModal));
|
||||
patched = patched.concat(patchPeekViewService(peekViewService));
|
||||
patched = patched.concat(patchEdgelessClipboard());
|
||||
if (!page.readonly) {
|
||||
patched = patchQuickSearchService(patched, framework);
|
||||
patched = patched.concat(patchQuickSearchService(framework));
|
||||
}
|
||||
if (shared) {
|
||||
patched = patchForSharedPage(patched);
|
||||
patched = patched.concat(patchForSharedPage());
|
||||
}
|
||||
patched = patchDocModeService(patched, docService, docsService);
|
||||
patched = patched.concat(patchDocModeService(docService, docsService));
|
||||
return patched;
|
||||
}, [
|
||||
confirmModal,
|
||||
@@ -179,7 +191,7 @@ export const BlocksuiteDocEditor = forwardRef<
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
const [specs, portals] = usePatchSpecs(page, !!shared, 'page');
|
||||
const [specs, portals] = usePatchSpecs(page, !!shared, DocMode.Page);
|
||||
|
||||
const settings = useLiveData(editorSettingService.editorSetting.settings$);
|
||||
|
||||
@@ -219,7 +231,7 @@ export const BlocksuiteEdgelessEditor = forwardRef<
|
||||
EdgelessEditor,
|
||||
BlocksuiteEditorProps
|
||||
>(function BlocksuiteEdgelessEditor({ page, shared }, ref) {
|
||||
const [specs, portals] = usePatchSpecs(page, !!shared, 'edgeless');
|
||||
const [specs, portals] = usePatchSpecs(page, !!shared, DocMode.Edgeless);
|
||||
const editorRef = useRef<EdgelessEditor | null>(null);
|
||||
|
||||
const onDocRef = useCallback(
|
||||
|
||||
@@ -3,7 +3,7 @@ import {
|
||||
AIImageBlockSpec,
|
||||
AIParagraphBlockSpec,
|
||||
} from '@affine/core/blocksuite/presets/ai';
|
||||
import type { BlockSpec } from '@blocksuite/block-std';
|
||||
import type { ExtensionType } from '@blocksuite/block-std';
|
||||
import {
|
||||
BookmarkBlockSpec,
|
||||
DatabaseBlockSpec,
|
||||
@@ -17,15 +17,12 @@ import {
|
||||
EmbedSyncedDocBlockSpec,
|
||||
EmbedYoutubeBlockSpec,
|
||||
ListBlockSpec,
|
||||
NoteBlockSpec,
|
||||
} from '@blocksuite/blocks';
|
||||
import { AIChatBlockSpec, EdgelessAIChatBlockSpec } from '@blocksuite/presets';
|
||||
|
||||
import { CustomAttachmentBlockSpec } from './custom/attachment-block';
|
||||
|
||||
export const CommonBlockSpecs: BlockSpec[] = [
|
||||
export const CommonBlockSpecs: ExtensionType[] = [
|
||||
ListBlockSpec,
|
||||
NoteBlockSpec,
|
||||
DatabaseBlockSpec,
|
||||
DataViewBlockSpec,
|
||||
DividerBlockSpec,
|
||||
@@ -42,6 +39,4 @@ export const CommonBlockSpecs: BlockSpec[] = [
|
||||
AICodeBlockSpec,
|
||||
AIImageBlockSpec,
|
||||
AIParagraphBlockSpec,
|
||||
AIChatBlockSpec,
|
||||
EdgelessAIChatBlockSpec,
|
||||
];
|
||||
].flat();
|
||||
|
||||
+17
-4
@@ -1,4 +1,9 @@
|
||||
import type { BlockSpec } from '@blocksuite/block-std';
|
||||
import {
|
||||
BlockFlavourIdentifier,
|
||||
BlockServiceIdentifier,
|
||||
type ExtensionType,
|
||||
StdIdentifier,
|
||||
} from '@blocksuite/block-std';
|
||||
import {
|
||||
AttachmentBlockService,
|
||||
AttachmentBlockSpec,
|
||||
@@ -14,7 +19,15 @@ class CustomAttachmentBlockService extends AttachmentBlockService {
|
||||
}
|
||||
}
|
||||
|
||||
export const CustomAttachmentBlockSpec: BlockSpec = {
|
||||
export const CustomAttachmentBlockSpec: ExtensionType[] = [
|
||||
...AttachmentBlockSpec,
|
||||
service: CustomAttachmentBlockService,
|
||||
};
|
||||
{
|
||||
setup: di => {
|
||||
di.override(
|
||||
BlockServiceIdentifier('affine:attachment'),
|
||||
CustomAttachmentBlockService,
|
||||
[StdIdentifier, BlockFlavourIdentifier('affine:attachment')]
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
+37
-18
@@ -3,12 +3,14 @@ import {
|
||||
AIPageRootBlockSpec,
|
||||
} from '@affine/core/blocksuite/presets/ai';
|
||||
import { mixpanel } from '@affine/core/mixpanel';
|
||||
import type {
|
||||
EdgelessRootBlockSpecType,
|
||||
PageRootBlockSpecType,
|
||||
RootService,
|
||||
TelemetryEventMap,
|
||||
} from '@blocksuite/blocks';
|
||||
import {
|
||||
BlockFlavourIdentifier,
|
||||
BlockServiceIdentifier,
|
||||
ConfigExtension,
|
||||
type ExtensionType,
|
||||
StdIdentifier,
|
||||
} from '@blocksuite/block-std';
|
||||
import type { RootService, TelemetryEventMap } from '@blocksuite/blocks';
|
||||
import {
|
||||
AffineCanvasTextFonts,
|
||||
EdgelessRootService,
|
||||
@@ -31,6 +33,7 @@ function customLoadFonts(service: RootService): void {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: make load fonts and telemetry service as BS extension
|
||||
function withAffineRootService(Service: typeof PageRootService) {
|
||||
return class extends Service {
|
||||
override loadFonts(): void {
|
||||
@@ -50,24 +53,40 @@ function withAffineRootService(Service: typeof PageRootService) {
|
||||
|
||||
export function createPageRootBlockSpec(
|
||||
framework: FrameworkProvider
|
||||
): PageRootBlockSpecType {
|
||||
return {
|
||||
): ExtensionType[] {
|
||||
return [
|
||||
...AIPageRootBlockSpec,
|
||||
service: withAffineRootService(PageRootService),
|
||||
config: {
|
||||
linkedWidget: createLinkedWidgetConfig(framework),
|
||||
{
|
||||
setup: di => {
|
||||
di.override(
|
||||
BlockServiceIdentifier('affine:page'),
|
||||
withAffineRootService(PageRootService),
|
||||
[StdIdentifier, BlockFlavourIdentifier('affine:page')]
|
||||
);
|
||||
},
|
||||
},
|
||||
};
|
||||
ConfigExtension('affine:page', {
|
||||
linkedWidget: createLinkedWidgetConfig(framework),
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
export function createEdgelessRootBlockSpec(
|
||||
framework: FrameworkProvider
|
||||
): EdgelessRootBlockSpecType {
|
||||
return {
|
||||
): ExtensionType[] {
|
||||
return [
|
||||
...AIEdgelessRootBlockSpec,
|
||||
service: withAffineRootService(EdgelessRootService as never),
|
||||
config: {
|
||||
linkedWidget: createLinkedWidgetConfig(framework),
|
||||
{
|
||||
setup: di => {
|
||||
di.override(
|
||||
BlockServiceIdentifier('affine:page'),
|
||||
withAffineRootService(EdgelessRootService as never),
|
||||
[StdIdentifier, BlockFlavourIdentifier('affine:page')]
|
||||
);
|
||||
},
|
||||
},
|
||||
};
|
||||
ConfigExtension('affine:page', {
|
||||
linkedWidget: createLinkedWidgetConfig(framework),
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
+150
-204
@@ -20,20 +20,31 @@ import {
|
||||
RecentDocsQuickSearchSession,
|
||||
} from '@affine/core/modules/quicksearch';
|
||||
import { DebugLogger } from '@affine/debug';
|
||||
import type { BlockSpec, WidgetComponent } from '@blocksuite/block-std';
|
||||
import {
|
||||
type AffineReference,
|
||||
type BlockService,
|
||||
BlockViewIdentifier,
|
||||
type ExtensionType,
|
||||
type WidgetComponent,
|
||||
} from '@blocksuite/block-std';
|
||||
import { BlockServiceWatcher } from '@blocksuite/block-std';
|
||||
import type {
|
||||
AffineReference,
|
||||
DatabaseBlockService,
|
||||
ListBlockService,
|
||||
ParagraphBlockService,
|
||||
RootService,
|
||||
} from '@blocksuite/blocks';
|
||||
import {
|
||||
AffineSlashMenuWidget,
|
||||
DocMode,
|
||||
DocModeProvider,
|
||||
EdgelessRootBlockComponent,
|
||||
EmbedLinkedDocBlockComponent,
|
||||
type ParagraphBlockService,
|
||||
type RootService,
|
||||
} from '@blocksuite/blocks';
|
||||
import { LinkIcon } from '@blocksuite/icons/rc';
|
||||
import { AIChatBlockSchema } from '@blocksuite/presets';
|
||||
import type { BlockSnapshot } from '@blocksuite/store';
|
||||
import {
|
||||
type DocMode,
|
||||
type DocService,
|
||||
DocsService,
|
||||
type FrameworkProvider,
|
||||
@@ -48,84 +59,62 @@ export type ReferenceReactRenderer = (
|
||||
|
||||
const logger = new DebugLogger('affine::spec-patchers');
|
||||
|
||||
function patchSpecService<Spec extends BlockSpec>(
|
||||
spec: Spec,
|
||||
onMounted: (
|
||||
service: Spec extends BlockSpec<any, infer BlockService>
|
||||
? BlockService
|
||||
: never
|
||||
) => (() => void) | void,
|
||||
function patchSpecService<Service extends BlockService = BlockService>(
|
||||
flavour: string,
|
||||
onMounted: (service: Service) => (() => void) | void,
|
||||
onWidgetConnected?: (component: WidgetComponent) => void
|
||||
) {
|
||||
const oldSetup = spec.setup;
|
||||
spec.setup = (slots, disposableGroup) => {
|
||||
oldSetup?.(slots, disposableGroup);
|
||||
disposableGroup.add(
|
||||
slots.mounted.on(({ service }) => {
|
||||
const disposable = onMounted(service as any);
|
||||
if (disposable) {
|
||||
disposableGroup.add(disposable);
|
||||
}
|
||||
})
|
||||
);
|
||||
class TempServiceWatcher extends BlockServiceWatcher {
|
||||
static override readonly flavour = flavour;
|
||||
override mounted() {
|
||||
super.mounted();
|
||||
const disposable = onMounted(this.blockService as any);
|
||||
const disposableGroup = this.blockService.disposables;
|
||||
if (disposable) {
|
||||
disposableGroup.add(disposable);
|
||||
}
|
||||
|
||||
onWidgetConnected &&
|
||||
disposableGroup.add(
|
||||
slots.widgetConnected.on(({ component }) => {
|
||||
onWidgetConnected(component);
|
||||
})
|
||||
);
|
||||
};
|
||||
return spec;
|
||||
if (onWidgetConnected) {
|
||||
disposableGroup.add(
|
||||
this.blockService.specSlots.widgetConnected.on(({ component }) => {
|
||||
onWidgetConnected(component);
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
return TempServiceWatcher;
|
||||
}
|
||||
|
||||
/**
|
||||
* Patch the block specs with custom renderers.
|
||||
*/
|
||||
export function patchReferenceRenderer(
|
||||
specs: BlockSpec[],
|
||||
reactToLit: (element: ElementOrFactory) => TemplateResult,
|
||||
reactRenderer: ReferenceReactRenderer
|
||||
) {
|
||||
): ExtensionType[] {
|
||||
const litRenderer = (reference: AffineReference) => {
|
||||
const node = reactRenderer(reference);
|
||||
return reactToLit(node);
|
||||
};
|
||||
|
||||
return specs.map(spec => {
|
||||
if (
|
||||
['affine:paragraph', 'affine:list', 'affine:database'].includes(
|
||||
spec.schema.model.flavour
|
||||
)
|
||||
) {
|
||||
spec = patchSpecService(
|
||||
spec as BlockSpec<string, ParagraphBlockService>,
|
||||
service => {
|
||||
service.referenceNodeConfig.setCustomContent(litRenderer);
|
||||
return () => {
|
||||
service.referenceNodeConfig.setCustomContent(null);
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
return spec;
|
||||
return ['affine:paragraph', 'affine:list', 'affine:database'].map(flavour => {
|
||||
return patchSpecService<
|
||||
ParagraphBlockService | ListBlockService | DatabaseBlockService
|
||||
>(flavour, service => {
|
||||
service.referenceNodeConfig.setCustomContent(litRenderer);
|
||||
return () => {
|
||||
service.referenceNodeConfig.setCustomContent(null);
|
||||
};
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function patchNotificationService(
|
||||
specs: BlockSpec[],
|
||||
{ closeConfirmModal, openConfirmModal }: ReturnType<typeof useConfirmModal>
|
||||
) {
|
||||
const rootSpec = specs.find(
|
||||
spec => spec.schema.model.flavour === 'affine:page'
|
||||
) as BlockSpec<string, RootService>;
|
||||
|
||||
if (!rootSpec) {
|
||||
return specs;
|
||||
}
|
||||
|
||||
patchSpecService(rootSpec, service => {
|
||||
export function patchNotificationService({
|
||||
closeConfirmModal,
|
||||
openConfirmModal,
|
||||
}: ReturnType<typeof useConfirmModal>) {
|
||||
return patchSpecService<RootService>('affine:page', service => {
|
||||
service.notificationService = {
|
||||
confirm: async ({ title, message, confirmText, cancelText, abort }) => {
|
||||
return new Promise<boolean>(resolve => {
|
||||
@@ -234,22 +223,10 @@ export function patchNotificationService(
|
||||
},
|
||||
};
|
||||
});
|
||||
return specs;
|
||||
}
|
||||
|
||||
export function patchPeekViewService(
|
||||
specs: BlockSpec[],
|
||||
service: PeekViewService
|
||||
) {
|
||||
const rootSpec = specs.find(
|
||||
spec => spec.schema.model.flavour === 'affine:page'
|
||||
) as BlockSpec<string, RootService>;
|
||||
|
||||
if (!rootSpec) {
|
||||
return specs;
|
||||
}
|
||||
|
||||
patchSpecService(rootSpec, pageService => {
|
||||
export function patchPeekViewService(service: PeekViewService) {
|
||||
return patchSpecService<RootService>('affine:page', pageService => {
|
||||
pageService.peekViewService = {
|
||||
peek: (target: ActivePeekView['target'], template?: TemplateResult) => {
|
||||
logger.debug('center peek', target, template);
|
||||
@@ -257,75 +234,55 @@ export function patchPeekViewService(
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
return specs;
|
||||
}
|
||||
|
||||
export function patchDocModeService(
|
||||
specs: BlockSpec[],
|
||||
docService: DocService,
|
||||
docsService: DocsService
|
||||
) {
|
||||
const rootSpec = specs.find(
|
||||
spec => spec.schema.model.flavour === 'affine:page'
|
||||
) as BlockSpec<string, RootService>;
|
||||
|
||||
if (!rootSpec) {
|
||||
return specs;
|
||||
): ExtensionType {
|
||||
const DEFAULT_MODE = 'page';
|
||||
class AffineDocModeService implements DocModeProvider {
|
||||
setMode = (mode: DocMode, id?: string) => {
|
||||
if (id) {
|
||||
docsService.list.setPrimaryMode(id, mode);
|
||||
} else {
|
||||
docService.doc.setPrimaryMode(mode);
|
||||
}
|
||||
};
|
||||
getMode = (id?: string) => {
|
||||
const mode = id
|
||||
? docsService.list.getPrimaryMode(id)
|
||||
: docService.doc.getPrimaryMode();
|
||||
return (mode || DEFAULT_MODE) as DocMode;
|
||||
};
|
||||
toggleMode = (id?: string) => {
|
||||
const mode = id
|
||||
? docsService.list.togglePrimaryMode(id)
|
||||
: docService.doc.togglePrimaryMode();
|
||||
return (mode || DEFAULT_MODE) as DocMode;
|
||||
};
|
||||
onModeChange = (handler: (mode: DocMode) => void, id?: string) => {
|
||||
// eslint-disable-next-line rxjs/finnish
|
||||
const mode$ = id
|
||||
? docsService.list.primaryMode$(id)
|
||||
: docService.doc.primaryMode$;
|
||||
const sub = mode$.subscribe(m => handler((m || DEFAULT_MODE) as DocMode));
|
||||
return {
|
||||
dispose: sub.unsubscribe,
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
patchSpecService(rootSpec, pageService => {
|
||||
const DEFAULT_MODE = 'page';
|
||||
pageService.docModeService = {
|
||||
setMode: (mode: DocMode, id?: string) => {
|
||||
if (id) {
|
||||
docsService.list.setPrimaryMode(id, mode);
|
||||
} else {
|
||||
docService.doc.setPrimaryMode(mode);
|
||||
}
|
||||
},
|
||||
getMode: (id?: string) => {
|
||||
const mode = id
|
||||
? docsService.list.getPrimaryMode(id)
|
||||
: docService.doc.getPrimaryMode();
|
||||
return mode || DEFAULT_MODE;
|
||||
},
|
||||
toggleMode: (id?: string) => {
|
||||
const mode = id
|
||||
? docsService.list.togglePrimaryMode(id)
|
||||
: docService.doc.togglePrimaryMode();
|
||||
return mode || DEFAULT_MODE;
|
||||
},
|
||||
onModeChange: (handler: (mode: DocMode) => void, id?: string) => {
|
||||
// eslint-disable-next-line rxjs/finnish
|
||||
const mode$ = id
|
||||
? docsService.list.primaryMode$(id)
|
||||
: docService.doc.primaryMode$;
|
||||
const sub = mode$.subscribe(m => handler(m || DEFAULT_MODE));
|
||||
return {
|
||||
dispose: sub.unsubscribe,
|
||||
};
|
||||
},
|
||||
};
|
||||
});
|
||||
const docModeExtension: ExtensionType = {
|
||||
setup: di => [di.override(DocModeProvider, AffineDocModeService)],
|
||||
};
|
||||
|
||||
return specs;
|
||||
return docModeExtension;
|
||||
}
|
||||
|
||||
export function patchQuickSearchService(
|
||||
specs: BlockSpec[],
|
||||
framework: FrameworkProvider
|
||||
) {
|
||||
const rootSpec = specs.find(
|
||||
spec => spec.schema.model.flavour === 'affine:page'
|
||||
) as BlockSpec<string, RootService>;
|
||||
|
||||
if (!rootSpec) {
|
||||
return specs;
|
||||
}
|
||||
|
||||
patchSpecService(
|
||||
rootSpec,
|
||||
export function patchQuickSearchService(framework: FrameworkProvider) {
|
||||
return patchSpecService<RootService>(
|
||||
'affine:page',
|
||||
pageService => {
|
||||
pageService.quickSearchService = {
|
||||
async searchDoc(options) {
|
||||
@@ -409,8 +366,8 @@ export function patchQuickSearchService(
|
||||
const docsService = framework.get(DocsService);
|
||||
const mode =
|
||||
result.id === 'creation:create-edgeless'
|
||||
? 'edgeless'
|
||||
: 'page';
|
||||
? DocMode.Edgeless
|
||||
: DocMode.Page;
|
||||
const newDoc = docsService.createDoc({
|
||||
primaryMode: mode,
|
||||
title: result.payload.title,
|
||||
@@ -490,63 +447,56 @@ export function patchQuickSearchService(
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return specs;
|
||||
}
|
||||
|
||||
export function patchEdgelessClipboard(specs: BlockSpec[]) {
|
||||
const rootSpec = specs.find(
|
||||
spec => spec.schema.model.flavour === 'affine:page'
|
||||
) as BlockSpec<string, RootService>;
|
||||
export function patchEdgelessClipboard() {
|
||||
class EdgelessClipboardWatcher extends BlockServiceWatcher {
|
||||
static override readonly flavour = 'affine:page';
|
||||
|
||||
if (!rootSpec) {
|
||||
return specs;
|
||||
}
|
||||
|
||||
const oldSetup = rootSpec.setup;
|
||||
rootSpec.setup = (slots, disposableGroup) => {
|
||||
oldSetup?.(slots, disposableGroup);
|
||||
disposableGroup.add(
|
||||
slots.viewConnected.on(view => {
|
||||
const component = view.component;
|
||||
if (component instanceof EdgelessRootBlockComponent) {
|
||||
const AIChatBlockFlavour = AIChatBlockSchema.model.flavour;
|
||||
const createFunc = (blocks: BlockSnapshot[]) => {
|
||||
const blockIds = blocks.map(({ props }) => {
|
||||
const {
|
||||
xywh,
|
||||
scale,
|
||||
messages,
|
||||
sessionId,
|
||||
rootDocId,
|
||||
rootWorkspaceId,
|
||||
} = props;
|
||||
const blockId = component.service.addBlock(
|
||||
AIChatBlockFlavour,
|
||||
{
|
||||
override mounted() {
|
||||
super.mounted();
|
||||
this.blockService.disposables.add(
|
||||
this.blockService.specSlots.viewConnected.on(view => {
|
||||
const { component } = view;
|
||||
if (component instanceof EdgelessRootBlockComponent) {
|
||||
const AIChatBlockFlavour = AIChatBlockSchema.model.flavour;
|
||||
const createFunc = (blocks: BlockSnapshot[]) => {
|
||||
const blockIds = blocks.map(({ props }) => {
|
||||
const {
|
||||
xywh,
|
||||
scale,
|
||||
messages,
|
||||
sessionId,
|
||||
rootDocId,
|
||||
rootWorkspaceId,
|
||||
},
|
||||
component.surface.model.id
|
||||
);
|
||||
return blockId;
|
||||
});
|
||||
return blockIds;
|
||||
};
|
||||
component.clipboardController.registerBlock(
|
||||
AIChatBlockFlavour,
|
||||
createFunc
|
||||
);
|
||||
}
|
||||
})
|
||||
);
|
||||
};
|
||||
} = props;
|
||||
const blockId = component.service.addBlock(
|
||||
AIChatBlockFlavour,
|
||||
{
|
||||
xywh,
|
||||
scale,
|
||||
messages,
|
||||
sessionId,
|
||||
rootDocId,
|
||||
rootWorkspaceId,
|
||||
},
|
||||
component.surface.model.id
|
||||
);
|
||||
return blockId;
|
||||
});
|
||||
return blockIds;
|
||||
};
|
||||
component.clipboardController.registerBlock(
|
||||
AIChatBlockFlavour,
|
||||
createFunc
|
||||
);
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return specs;
|
||||
return EdgelessClipboardWatcher;
|
||||
}
|
||||
|
||||
@customElement('affine-linked-doc-ref-block')
|
||||
@@ -557,22 +507,18 @@ export class LinkedDocBlockComponent extends EmbedLinkedDocBlockComponent {
|
||||
}
|
||||
}
|
||||
|
||||
export function patchForSharedPage(specs: BlockSpec[]) {
|
||||
return specs.map(spec => {
|
||||
const linkedDocNames = [
|
||||
'affine:embed-linked-doc',
|
||||
'affine:embed-synced-doc',
|
||||
];
|
||||
|
||||
if (linkedDocNames.includes(spec.schema.model.flavour)) {
|
||||
spec = {
|
||||
...spec,
|
||||
view: {
|
||||
component: literal`affine-linked-doc-ref-block`,
|
||||
widgets: {},
|
||||
},
|
||||
};
|
||||
}
|
||||
return spec;
|
||||
});
|
||||
export function patchForSharedPage() {
|
||||
const extension: ExtensionType = {
|
||||
setup: di => {
|
||||
di.override(
|
||||
BlockViewIdentifier('affine:embed-linked-doc'),
|
||||
() => literal`affine-linked-doc-ref-block`
|
||||
);
|
||||
di.override(
|
||||
BlockViewIdentifier('affine:embed-synced-doc'),
|
||||
() => literal`affine-linked-doc-ref-block`
|
||||
);
|
||||
},
|
||||
};
|
||||
return extension;
|
||||
}
|
||||
|
||||
+5
-3
@@ -1,4 +1,4 @@
|
||||
import type { BlockSpec } from '@blocksuite/block-std';
|
||||
import type { ExtensionType } from '@blocksuite/block-std';
|
||||
import {
|
||||
EdgelessNoteBlockSpec,
|
||||
EdgelessSurfaceBlockSpec,
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
EdgelessTextBlockSpec,
|
||||
FrameBlockSpec,
|
||||
} from '@blocksuite/blocks';
|
||||
import { EdgelessAIChatBlockSpec } from '@blocksuite/presets';
|
||||
import type { FrameworkProvider } from '@toeverything/infra';
|
||||
|
||||
import { CommonBlockSpecs } from './common';
|
||||
@@ -13,7 +14,7 @@ import { createEdgelessRootBlockSpec } from './custom/root-block';
|
||||
|
||||
export function createEdgelessModeSpecs(
|
||||
framework: FrameworkProvider
|
||||
): BlockSpec[] {
|
||||
): ExtensionType[] {
|
||||
return [
|
||||
...CommonBlockSpecs,
|
||||
EdgelessSurfaceBlockSpec,
|
||||
@@ -21,7 +22,8 @@ export function createEdgelessModeSpecs(
|
||||
FrameBlockSpec,
|
||||
EdgelessTextBlockSpec,
|
||||
EdgelessNoteBlockSpec,
|
||||
EdgelessAIChatBlockSpec,
|
||||
// special
|
||||
createEdgelessRootBlockSpec(framework),
|
||||
];
|
||||
].flat();
|
||||
}
|
||||
|
||||
@@ -1,19 +1,25 @@
|
||||
import type { BlockSpec } from '@blocksuite/block-std';
|
||||
import type { ExtensionType } from '@blocksuite/block-std';
|
||||
import {
|
||||
NoteBlockSpec,
|
||||
PageSurfaceBlockSpec,
|
||||
PageSurfaceRefBlockSpec,
|
||||
} from '@blocksuite/blocks';
|
||||
import { AIChatBlockSpec } from '@blocksuite/presets';
|
||||
import { type FrameworkProvider } from '@toeverything/infra';
|
||||
|
||||
import { CommonBlockSpecs } from './common';
|
||||
import { createPageRootBlockSpec } from './custom/root-block';
|
||||
|
||||
export function createPageModeSpecs(framework: FrameworkProvider): BlockSpec[] {
|
||||
export function createPageModeSpecs(
|
||||
framework: FrameworkProvider
|
||||
): ExtensionType[] {
|
||||
return [
|
||||
...CommonBlockSpecs,
|
||||
PageSurfaceBlockSpec,
|
||||
PageSurfaceRefBlockSpec,
|
||||
NoteBlockSpec,
|
||||
AIChatBlockSpec,
|
||||
// special
|
||||
createPageRootBlockSpec(framework),
|
||||
];
|
||||
].flat();
|
||||
}
|
||||
|
||||
+4
-4
@@ -1,10 +1,10 @@
|
||||
import type { BlockSpec } from '@blocksuite/block-std';
|
||||
import type { ExtensionType } from '@blocksuite/block-std';
|
||||
import { SpecProvider } from '@blocksuite/blocks';
|
||||
import { AIChatBlockSpec, EdgelessAIChatBlockSpec } from '@blocksuite/presets';
|
||||
import { EdgelessAIChatBlockSpec } from '@blocksuite/presets';
|
||||
|
||||
const CustomSpecs: BlockSpec[] = [AIChatBlockSpec, EdgelessAIChatBlockSpec];
|
||||
const CustomSpecs: ExtensionType[] = [EdgelessAIChatBlockSpec].flat();
|
||||
|
||||
function patchPreviewSpec(id: string, specs: BlockSpec[]) {
|
||||
function patchPreviewSpec(id: string, specs: ExtensionType[]) {
|
||||
const specProvider = SpecProvider.getInstance();
|
||||
specProvider.extendSpec(id, specs);
|
||||
}
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@ export const usePresent = () => {
|
||||
|
||||
// TODO(@catsjuice): use surfaceService subAtom
|
||||
const enterOrLeavePresentationMode = () => {
|
||||
const edgelessRootService = editorHost.spec.getService(
|
||||
const edgelessRootService = editorHost.std.getService(
|
||||
'affine:page'
|
||||
) as EdgelessRootService;
|
||||
|
||||
|
||||
@@ -3,8 +3,9 @@ import { registerAffineCommand } from '@affine/core/commands';
|
||||
import { track } from '@affine/core/mixpanel';
|
||||
import { EditorService } from '@affine/core/modules/editor';
|
||||
import { useI18n } from '@affine/i18n';
|
||||
import { DocMode } from '@blocksuite/blocks';
|
||||
import { EdgelessIcon, PageIcon } from '@blocksuite/icons/rc';
|
||||
import { type DocMode, useLiveData, useService } from '@toeverything/infra';
|
||||
import { useLiveData, useService } from '@toeverything/infra';
|
||||
import { useCallback, useEffect, useMemo } from 'react';
|
||||
|
||||
import { switchItem } from './style.css';
|
||||
@@ -38,16 +39,16 @@ export const EditorModeSwitch = () => {
|
||||
|
||||
const togglePage = useCallback(() => {
|
||||
if (currentMode === 'page' || isSharedMode || trash) return;
|
||||
editor.setMode('page');
|
||||
editor.doc.setPrimaryMode('page');
|
||||
editor.setMode(DocMode.Page);
|
||||
editor.doc.setPrimaryMode(DocMode.Page);
|
||||
toast(t['com.affine.toastMessage.pageMode']());
|
||||
track.$.header.actions.switchPageMode({ mode: 'page' });
|
||||
}, [currentMode, editor, isSharedMode, t, trash]);
|
||||
|
||||
const toggleEdgeless = useCallback(() => {
|
||||
if (currentMode === 'edgeless' || isSharedMode || trash) return;
|
||||
editor.setMode('edgeless');
|
||||
editor.doc.setPrimaryMode('edgeless');
|
||||
editor.setMode(DocMode.Edgeless);
|
||||
editor.doc.setPrimaryMode(DocMode.Edgeless);
|
||||
toast(t['com.affine.toastMessage.edgelessMode']());
|
||||
track.$.header.actions.switchPageMode({ mode: 'edgeless' });
|
||||
}, [currentMode, editor, isSharedMode, t, trash]);
|
||||
@@ -78,7 +79,10 @@ export const EditorModeSwitch = () => {
|
||||
binding: 'Alt+KeyS',
|
||||
capture: true,
|
||||
},
|
||||
run: () => onModeChange(currentMode === 'edgeless' ? 'page' : 'edgeless'),
|
||||
run: () =>
|
||||
onModeChange(
|
||||
currentMode === 'edgeless' ? DocMode.Page : DocMode.Edgeless
|
||||
),
|
||||
});
|
||||
}, [currentMode, isSharedMode, onModeChange, t, trash]);
|
||||
|
||||
@@ -93,8 +97,8 @@ export const EditorModeSwitch = () => {
|
||||
<PureEditorModeSwitch
|
||||
mode={currentMode}
|
||||
setMode={onModeChange}
|
||||
hidePage={shouldHide('page')}
|
||||
hideEdgeless={shouldHide('edgeless')}
|
||||
hidePage={shouldHide(DocMode.Page)}
|
||||
hideEdgeless={shouldHide(DocMode.Edgeless)}
|
||||
/>
|
||||
</div>
|
||||
</Tooltip>
|
||||
|
||||
@@ -2,8 +2,8 @@ import { toast } from '@affine/component';
|
||||
import { useDocCollectionHelper } from '@affine/core/hooks/use-block-suite-workspace-helper';
|
||||
import { EditorSettingService } from '@affine/core/modules/editor-settting';
|
||||
import { WorkbenchService } from '@affine/core/modules/workbench';
|
||||
import type { DocMode } from '@blocksuite/blocks';
|
||||
import {
|
||||
type DocMode,
|
||||
DocsService,
|
||||
initEmptyPage,
|
||||
useLiveData,
|
||||
@@ -29,7 +29,7 @@ export const usePageHelper = (docCollection: DocCollection) => {
|
||||
const page = createDoc();
|
||||
initEmptyPage(page);
|
||||
const primaryMode = mode || settings.newDocDefaultMode;
|
||||
docRecordList.doc$(page.id).value?.setPrimaryMode(primaryMode);
|
||||
docRecordList.doc$(page.id).value?.setPrimaryMode(primaryMode as DocMode);
|
||||
if (open !== false)
|
||||
workbench.openDoc(page.id, {
|
||||
at: open === 'new-tab' ? 'new-tab' : 'active',
|
||||
@@ -41,7 +41,7 @@ export const usePageHelper = (docCollection: DocCollection) => {
|
||||
|
||||
const createEdgelessAndOpen = useCallback(
|
||||
(open?: boolean | 'new-tab') => {
|
||||
return createPageAndOpen('edgeless', open);
|
||||
return createPageAndOpen('edgeless' as DocMode, open);
|
||||
},
|
||||
[createPageAndOpen]
|
||||
);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { AuthService } from '@affine/core/modules/cloud';
|
||||
import { type DocMode, useLiveData, useService } from '@toeverything/infra';
|
||||
import type { DocMode } from '@blocksuite/blocks';
|
||||
import { useLiveData, useService } from '@toeverything/infra';
|
||||
|
||||
import { PresentButton } from './present';
|
||||
import { SignIn } from './sign-in';
|
||||
|
||||
@@ -1,15 +1,20 @@
|
||||
import './page-detail-editor.css';
|
||||
|
||||
import { useDocCollectionPage } from '@affine/core/hooks/use-block-suite-workspace-page';
|
||||
import { ViewService } from '@affine/core/modules/workbench/services/view';
|
||||
import type { DocMode } from '@blocksuite/blocks';
|
||||
import { DisposableGroup } from '@blocksuite/global/utils';
|
||||
import type { AffineEditorContainer } from '@blocksuite/presets';
|
||||
import type { Doc as BlockSuiteDoc, DocCollection } from '@blocksuite/store';
|
||||
import { type DocMode, useLiveData, useService } from '@toeverything/infra';
|
||||
import {
|
||||
useLiveData,
|
||||
useService,
|
||||
useServiceOptional,
|
||||
} from '@toeverything/infra';
|
||||
import { cssVar } from '@toeverything/theme';
|
||||
import clsx from 'clsx';
|
||||
import type { CSSProperties } from 'react';
|
||||
import { memo, Suspense, useCallback, useMemo } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
|
||||
import { EditorService } from '../modules/editor';
|
||||
import {
|
||||
@@ -37,14 +42,27 @@ export interface PageDetailEditorProps {
|
||||
onLoad?: OnLoadEditor;
|
||||
}
|
||||
|
||||
function useRouterHash() {
|
||||
return useLocation().hash.substring(1);
|
||||
}
|
||||
|
||||
const PageDetailEditorMain = memo(function PageDetailEditorMain({
|
||||
page,
|
||||
onLoad,
|
||||
}: PageDetailEditorProps & { page: BlockSuiteDoc }) {
|
||||
const viewService = useServiceOptional(ViewService);
|
||||
const params = useLiveData(
|
||||
viewService?.view.queryString$<{
|
||||
mode?: string;
|
||||
blockIds?: string[];
|
||||
elementIds?: string[];
|
||||
}>({
|
||||
// Cannot handle single id situation correctly: `blockIds=xxx`
|
||||
arrayFormat: 'none',
|
||||
types: {
|
||||
mode: 'string',
|
||||
blockIds: value => (value.length ? value.split(',') : []),
|
||||
elementIds: value => (value.length ? value.split(',') : []),
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
const editor = useService(EditorService).editor;
|
||||
const mode = useLiveData(editor.mode$);
|
||||
|
||||
@@ -66,8 +84,6 @@ const PageDetailEditorMain = memo(function PageDetailEditorMain({
|
||||
: fontStyle.value;
|
||||
}, [settings.customFontFamily, settings.fontFamily]);
|
||||
|
||||
const blockId = useRouterHash();
|
||||
|
||||
const onLoadEditor = useCallback(
|
||||
(editor: AffineEditorContainer) => {
|
||||
// debug current detail editor
|
||||
@@ -106,7 +122,8 @@ const PageDetailEditorMain = memo(function PageDetailEditorMain({
|
||||
mode={mode}
|
||||
page={page}
|
||||
shared={isSharedMode}
|
||||
defaultSelectedBlockId={blockId}
|
||||
blockIds={params?.blockIds}
|
||||
elementIds={params?.elementIds}
|
||||
onLoadEditor={onLoadEditor}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -13,6 +13,7 @@ import { TagService } from '@affine/core/modules/tag';
|
||||
import { isNewTabTrigger } from '@affine/core/utils';
|
||||
import type { Collection } from '@affine/env/filter';
|
||||
import { useI18n } from '@affine/i18n';
|
||||
import type { DocMode } from '@blocksuite/blocks';
|
||||
import {
|
||||
ArrowDownSmallIcon,
|
||||
SearchIcon,
|
||||
@@ -79,7 +80,7 @@ export const PageListHeader = () => {
|
||||
createEdgeless(isNewTabTrigger(e) ? 'new-tab' : true)
|
||||
}
|
||||
onCreatePage={e =>
|
||||
createPage('page', isNewTabTrigger(e) ? 'new-tab' : true)
|
||||
createPage('page' as DocMode, isNewTabTrigger(e) ? 'new-tab' : true)
|
||||
}
|
||||
onCreateDoc={e =>
|
||||
createPage(undefined, isNewTabTrigger(e) ? 'new-tab' : true)
|
||||
@@ -144,7 +145,10 @@ export const CollectionPageListHeader = ({
|
||||
[openConfirmModal, t, createAndAddDocument]
|
||||
);
|
||||
|
||||
const createPageModeDoc = useCallback(() => createPage('page'), [createPage]);
|
||||
const createPageModeDoc = useCallback(
|
||||
() => createPage('page' as DocMode),
|
||||
[createPage]
|
||||
);
|
||||
|
||||
const onCreateEdgeless = useCallback(
|
||||
() => onConfirmAddDocument(createEdgeless),
|
||||
|
||||
@@ -13,6 +13,7 @@ import { CMDKQuickSearchService } from '@affine/core/modules/quicksearch/service
|
||||
import { isNewTabTrigger } from '@affine/core/utils';
|
||||
import { events } from '@affine/electron-api';
|
||||
import { useI18n } from '@affine/i18n';
|
||||
import type { DocMode } from '@blocksuite/blocks';
|
||||
import {
|
||||
AllDocsIcon,
|
||||
GithubIcon,
|
||||
@@ -110,7 +111,7 @@ export const RootAppSidebar = (): ReactElement => {
|
||||
const onClickNewPage = useAsyncCallback(
|
||||
async (e?: MouseEvent) => {
|
||||
const page = pageHelper.createPage(
|
||||
settings.newDocDefaultMode,
|
||||
settings.newDocDefaultMode as DocMode,
|
||||
isNewTabTrigger(e) ? 'new-tab' : true
|
||||
);
|
||||
page.load();
|
||||
|
||||
Reference in New Issue
Block a user