(null);
@@ -208,27 +210,61 @@ export const BlocksuiteEditorContainer = forwardRef<
const blockElement = useBlockElementById(rootRef, defaultSelectedBlockId);
useEffect(() => {
- if (blockElement) {
- affineEditorContainerProxy.updateComplete
- .then(() => {
- if (mode === 'page') {
- blockElement.scrollIntoView({
- behavior: 'smooth',
- block: 'center',
- });
- }
- const selectManager = affineEditorContainerProxy.host?.selection;
- if (!blockElement.path.length || !selectManager) {
- return;
- }
- const newSelection = selectManager.create('block', {
- path: blockElement.path,
- });
- selectManager.set([newSelection]);
- })
- .catch(console.error);
- }
- }, [blockElement, affineEditorContainerProxy, mode]);
+ let disposable: Disposable | undefined = undefined;
+
+ // update the hash when the block is selected
+ const handleUpdateComplete = () => {
+ const selectManager = affineEditorContainerProxy?.host?.selection;
+ if (!selectManager) return;
+
+ disposable = selectManager.slots.changed.on(() => {
+ const selectedBlock = selectManager.find('block');
+ const selectedId = selectedBlock?.blockId;
+
+ const newHash = selectedId ? `#${selectedId}` : '';
+ //TODO: use activeView.history which is in workbench instead of history.replaceState
+ history.replaceState(null, '', `${window.location.pathname}${newHash}`);
+
+ // Dispatch a custom event to notify the hash change
+ const hashChangeEvent = new CustomEvent('hashchange-custom', {
+ detail: { hash: newHash },
+ });
+ window.dispatchEvent(hashChangeEvent);
+ });
+ };
+
+ // scroll to the block element when the block id is provided and the page is first loaded
+ const handleScrollToBlock = (blockElement: BlockElement) => {
+ if (mode === 'page') {
+ blockElement.scrollIntoView({
+ behavior: 'smooth',
+ block: 'center',
+ });
+ }
+ const selectManager = affineEditorContainerProxy.host?.selection;
+ if (!blockElement.path.length || !selectManager) {
+ return;
+ }
+ const newSelection = selectManager.create('block', {
+ path: blockElement.path,
+ });
+ selectManager.set([newSelection]);
+ setScrolled(true);
+ };
+
+ affineEditorContainerProxy.updateComplete
+ .then(() => {
+ if (blockElement && !scrolled) {
+ handleScrollToBlock(blockElement);
+ }
+ handleUpdateComplete();
+ })
+ .catch(console.error);
+
+ return () => {
+ disposable?.dispose();
+ };
+ }, [blockElement, affineEditorContainerProxy, mode, scrolled]);
return (
{
+ const unsubs: Array<() => void> = [];
+
+ unsubs.push(
+ registerAffineCommand({
+ id: `affine:share-private-link:${docId}`,
+ category: 'affine:general',
+ preconditionStrategy: () => isActiveView,
+ keyBinding: {
+ binding: '$mod+Shift+c',
+ },
+ label: '',
+ icon: null,
+ run() {
+ isActiveView && onClickCopyLink();
+ },
+ })
+ );
+ return () => {
+ unsubs.forEach(unsub => unsub());
+ };
+ }, [docId, isActiveView, onClickCopyLink]);
+}
diff --git a/packages/frontend/core/src/components/affine/share-page-modal/share-menu/use-share-url.ts b/packages/frontend/core/src/hooks/affine/use-share-url.ts
similarity index 72%
rename from packages/frontend/core/src/components/affine/share-page-modal/share-menu/use-share-url.ts
rename to packages/frontend/core/src/hooks/affine/use-share-url.ts
index 83db694965..dfa0fc707f 100644
--- a/packages/frontend/core/src/components/affine/share-page-modal/share-menu/use-share-url.ts
+++ b/packages/frontend/core/src/hooks/affine/use-share-url.ts
@@ -2,7 +2,7 @@ import { toast } from '@affine/component';
import { getAffineCloudBaseUrl } from '@affine/core/modules/cloud/services/fetch';
import { mixpanel } from '@affine/core/utils';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
-import { useCallback, useMemo } from 'react';
+import { useCallback, useEffect, useMemo, useState } from 'react';
type UrlType = 'share' | 'workspace';
@@ -14,9 +14,24 @@ type UseSharingUrl = {
const useGenerateUrl = ({ workspaceId, pageId, urlType }: UseSharingUrl) => {
// to generate a private url like https://app.affine.app/workspace/123/456
+ // or https://app.affine.app/workspace/123/456#block-123
+
// to generate a public url like https://app.affine.app/share/123/456
// or https://app.affine.app/share/123/456?mode=edgeless
+ const [hash, setHash] = useState(window.location.hash);
+
+ useEffect(() => {
+ const handleLocationChange = () => {
+ setHash(window.location.hash);
+ };
+ window.addEventListener('hashchange-custom', handleLocationChange);
+
+ return () => {
+ window.removeEventListener('hashchange-custom', handleLocationChange);
+ };
+ }, [setHash]);
+
const baseUrl = getAffineCloudBaseUrl();
const url = useMemo(() => {
@@ -25,12 +40,12 @@ const useGenerateUrl = ({ workspaceId, pageId, urlType }: UseSharingUrl) => {
try {
return new URL(
- `${baseUrl}/${urlType}/${workspaceId}/${pageId}`
+ `${baseUrl}/${urlType}/${workspaceId}/${pageId}${urlType === 'workspace' ? `${hash}` : ''}`
).toString();
} catch (e) {
return null;
}
- }, [baseUrl, pageId, urlType, workspaceId]);
+ }, [baseUrl, hash, pageId, urlType, workspaceId]);
return url;
};
diff --git a/packages/frontend/core/src/hooks/affine/use-shortcuts.ts b/packages/frontend/core/src/hooks/affine/use-shortcuts.ts
index c0e73761a3..0ed473d3b6 100644
--- a/packages/frontend/core/src/hooks/affine/use-shortcuts.ts
+++ b/packages/frontend/core/src/hooks/affine/use-shortcuts.ts
@@ -42,7 +42,8 @@ type KeyboardShortcutsI18NKeys =
| 'groupDatabase'
| 'moveUp'
| 'moveDown'
- | 'divider';
+ | 'divider'
+ | 'copy-private-link';
// TODO(550): remove this hook after 'useAFFiNEI18N' support scoped i18n
const useKeyboardShortcutsI18N = () => {
@@ -81,8 +82,9 @@ export const useWinGeneralKeyboardShortcuts = (): ShortcutMap => {
// not implement yet
// [t('appendDailyNote')]: 'Ctrl + Alt + A',
[t('expandOrCollapseSidebar')]: ['Ctrl', '/'],
- [t('goBack')]: ['Ctrl + ['],
- [t('goForward')]: ['Ctrl + ]'],
+ [t('goBack')]: ['Ctrl', '['],
+ [t('goForward')]: ['Ctrl', ']'],
+ [t('copy-private-link')]: ['⌘', '⇧', 'C'],
}),
[t]
);
@@ -97,8 +99,9 @@ export const useMacGeneralKeyboardShortcuts = (): ShortcutMap => {
// not implement yet
// [t('appendDailyNote')]: '⌘ + ⌥ + A',
[t('expandOrCollapseSidebar')]: ['⌘', '/'],
- [t('goBack')]: ['⌘ + ['],
- [t('goForward')]: ['⌘ + ]'],
+ [t('goBack')]: ['⌘ ', '['],
+ [t('goForward')]: ['⌘ ', ']'],
+ [t('copy-private-link')]: ['⌘', '⇧', 'C'],
}),
[t]
);
diff --git a/packages/frontend/i18n/src/resources/en.json b/packages/frontend/i18n/src/resources/en.json
index 77c41eded6..22570febc3 100644
--- a/packages/frontend/i18n/src/resources/en.json
+++ b/packages/frontend/i18n/src/resources/en.json
@@ -1322,5 +1322,6 @@
"will be moved to Trash": "{{title}} will be moved to Trash",
"com.affine.ai-onboarding.edgeless.get-started": "Get Started",
"com.affine.ai-onboarding.edgeless.purchase": "Upgrade to Unlimited Usage",
- "will delete member": "will delete member"
+ "will delete member": "will delete member",
+ "com.affine.keyboardShortcuts.copy-private-link": "Copy Private Link"
}