Skip to content

Commit

Permalink
feat: generate url service (#8813)
Browse files Browse the repository at this point in the history
Closes: [BS-1961](https://linear.app/affine-design/issue/BS-1961/实现-generatedocurlservice)

### What's Changed

In the `Alias` ​​function, we need to copy the link, and need a service that can generate a complete link.

Complementary with `ParseDocUrlService`.

https://github.com/user-attachments/assets/7c5c1927-f942-4f05-9b1f-1f1c1cb6d740
  • Loading branch information
fundon committed Dec 3, 2024
1 parent 73e85f6 commit 8ecc20c
Show file tree
Hide file tree
Showing 5 changed files with 128 additions and 25 deletions.
22 changes: 22 additions & 0 deletions packages/affine/shared/src/services/generate-url-service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import type { ReferenceParams } from '@blocksuite/affine-model';
import type { ExtensionType } from '@blocksuite/block-std';

import { createIdentifier } from '@blocksuite/global/di';

export interface GenerateDocUrlService {
generateDocUrl: (docId: string, params?: ReferenceParams) => string | void;
}

export const GenerateDocUrlProvider = createIdentifier<GenerateDocUrlService>(
'GenerateDocUrlService'
);

export function GenerateDocUrlExtension(
generateDocUrlProvider: GenerateDocUrlService
): ExtensionType {
return {
setup: di => {
di.addImpl(GenerateDocUrlProvider, generateDocUrlProvider);
},
};
}
1 change: 1 addition & 0 deletions packages/affine/shared/src/services/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export * from './edit-props-store.js';
export * from './editor-setting-service.js';
export * from './embed-option-service.js';
export * from './font-loader/index.js';
export * from './generate-url-service.js';
export * from './notification-service.js';
export * from './parse-url-service.js';
export * from './quick-search-service.js';
Expand Down
25 changes: 19 additions & 6 deletions packages/playground/apps/_common/components/docs-panel.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import type { AffineEditorContainer } from '@blocksuite/presets';
import type { DocCollection } from '@blocksuite/store';
import type { BlockCollection, DocCollection } from '@blocksuite/store';

import { ShadowlessElement } from '@blocksuite/block-std';
import { CloseIcon, createDefaultDoc } from '@blocksuite/blocks';
import {
CloseIcon,
createDefaultDoc,
GenerateDocUrlProvider,
} from '@blocksuite/blocks';
import { WithDisposable } from '@blocksuite/global/utils';
import { css, html, nothing } from 'lit';
import { customElement, property } from 'lit/decorators.js';
Expand Down Expand Up @@ -60,6 +64,18 @@ export class DocsPanel extends WithDisposable(ShadowlessElement) {
createDocBlock(this.editor.doc.collection);
};

gotoDoc = (doc: BlockCollection) => {
const url = this.editor.std
.get(GenerateDocUrlProvider)
.generateDocUrl(doc.id);
if (url) history.pushState({}, '', url);

this.editor.doc = doc.getDoc();
this.editor.doc.load();
this.editor.doc.resetHistory();
this.requestUpdate();
};

private get collection() {
return this.editor.doc.collection;
}
Expand Down Expand Up @@ -97,10 +113,7 @@ export class DocsPanel extends WithDisposable(ShadowlessElement) {
justifyContent: 'space-between',
});
const click = () => {
this.editor.doc = doc.getDoc();
this.editor.doc.load();
this.editor.doc.resetHistory();
this.requestUpdate();
this.gotoDoc(doc);
};
const deleteDoc = (e: MouseEvent) => {
e.stopPropagation();
Expand Down
31 changes: 26 additions & 5 deletions packages/playground/apps/_common/mock-services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@ import {
ColorScheme,
type DocMode,
type DocModeProvider,
type GenerateDocUrlService,
matchFlavours,
type NotificationService,
type ParseDocUrlService,
type ReferenceParams,
type ThemeExtension,
toast,
} from '@blocksuite/blocks';
Expand Down Expand Up @@ -109,12 +111,10 @@ export function mockParseDocUrlService(collection: DocCollection) {
const parseDocUrlService: ParseDocUrlService = {
parseDocUrl: (url: string) => {
if (url && URL.canParse(url)) {
const path = new URL(url).pathname;
const path = decodeURIComponent(new URL(url).hash.slice(1));
const item =
path.length > 1
? [...collection.docs.values()].find(doc => {
return doc.meta?.title === path.slice(1);
})
path.length > 0
? [...collection.docs.values()].find(doc => doc.id === path)
: null;
if (item) {
return {
Expand Down Expand Up @@ -181,3 +181,24 @@ export function mockPeekViewExtension(
},
} satisfies PeekViewService);
}

export function mockGenerateDocUrlService(collection: DocCollection) {
const generateDocUrlService: GenerateDocUrlService = {
generateDocUrl: (docId: string, params?: ReferenceParams) => {
const doc = collection.getDoc(docId);
if (!doc) return;

const url = new URL(location.origin);
if (params) {
const search = url.searchParams;
for (const [key, value] of Object.entries(params)) {
search.set(key, Array.isArray(value) ? value.join(',') : value);
}
}
url.hash = encodeURIComponent(docId);

return url.toString();
},
};
return generateDocUrlService;
}
74 changes: 60 additions & 14 deletions packages/playground/apps/default/utils/editor.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import type { EditorHost, ExtensionType } from '@blocksuite/block-std';
import type { BlockCollection, DocCollection } from '@blocksuite/store';
import type { BlockCollection, Doc, DocCollection } from '@blocksuite/store';

import {
CommunityCanvasTextFonts,
DocModeExtension,
DocModeProvider,
FontConfigExtension,
GenerateDocUrlExtension,
GenerateDocUrlProvider,
NotificationExtension,
OverrideThemeExtension,
ParseDocUrlExtension,
Expand All @@ -22,16 +24,44 @@ import { LeftSidePanel } from '../../_common/components/left-side-panel.js';
import { QuickEdgelessMenu } from '../../_common/components/quick-edgeless-menu.js';
import {
mockDocModeService,
mockGenerateDocUrlService,
mockNotificationService,
mockParseDocUrlService,
mockPeekViewExtension,
themeExtension,
} from '../../_common/mock-services.js';
import { getExampleSpecs } from '../specs-examples/index.js';

function setDocModeFromUrlParams(service: DocModeProvider, docId: string) {
const params = new URLSearchParams(location.search);
const paramMode = params.get('mode');
function getDocFromUrlParams(collection: DocCollection, url: URL) {
let doc: Doc | null = null;

const docId = decodeURIComponent(url.hash.slice(1));

if (docId) {
doc = collection.getDoc(docId);
}
if (!doc) {
const blockCollection = collection.docs.values().next()
.value as BlockCollection;
assertExists(blockCollection, 'Need to create a doc first');
doc = blockCollection.getDoc();
}

doc.load();
doc.resetHistory();

assertExists(doc.ready, 'Doc is not ready');
assertExists(doc.root, 'Doc root is not ready');

return doc;
}

function setDocModeFromUrlParams(
service: DocModeProvider,
search: URLSearchParams,
docId: string
) {
const paramMode = search.get('mode');
if (paramMode) {
const docMode = paramMode === 'page' ? 'page' : 'edgeless';
service.setPrimaryMode(docMode, docId);
Expand All @@ -40,17 +70,12 @@ function setDocModeFromUrlParams(service: DocModeProvider, docId: string) {
}

export async function mountDefaultDocEditor(collection: DocCollection) {
const blockCollection = collection.docs.values().next()
.value as BlockCollection;
assertExists(blockCollection, 'Need to create a doc first');
const doc = blockCollection.getDoc();

assertExists(doc.ready, 'Doc is not ready');
assertExists(doc.root, 'Doc root is not ready');

const app = document.getElementById('app');
if (!app) return;

const url = new URL(location.toString());
const doc = getDocFromUrlParams(collection, url);

const attachmentViewerPanel = new AttachmentViewerPanel();

const editor = new AffineEditorContainer();
Expand All @@ -77,6 +102,12 @@ export async function mountDefaultDocEditor(collection: DocCollection) {
if (!target) {
throw new Error(`Failed to jump to doc ${docId}`);
}

const url = editor.std
.get(GenerateDocUrlProvider)
.generateDocUrl(target.id);
if (url) history.pushState({}, '', url);

target.load();
editor.doc = target;
});
Expand All @@ -85,7 +116,7 @@ export async function mountDefaultDocEditor(collection: DocCollection) {
await editor.updateComplete;
const modeService = editor.host!.std.get(DocModeProvider);
editor.mode = modeService.getPrimaryMode(doc.id);
setDocModeFromUrlParams(modeService, doc.id);
setDocModeFromUrlParams(modeService, url.searchParams, doc.id);
editor.slots.docUpdated.on(({ newDocId }) => {
editor.mode = modeService.getPrimaryMode(newDocId);
});
Expand All @@ -96,7 +127,7 @@ export async function mountDefaultDocEditor(collection: DocCollection) {
docsPanel.editor = editor;

const quickEdgelessMenu = new QuickEdgelessMenu();
quickEdgelessMenu.collection = doc.collection;
quickEdgelessMenu.collection = collection;
quickEdgelessMenu.editor = editor;
quickEdgelessMenu.leftSidePanel = leftSidePanel;
quickEdgelessMenu.docsPanel = docsPanel;
Expand All @@ -119,6 +150,20 @@ export async function mountDefaultDocEditor(collection: DocCollection) {
},
});

window.addEventListener('hashchange', () => {
const url = new URL(location.toString());
const doc = getDocFromUrlParams(collection, url);
if (!doc) return;

if (docsPanel.checkVisibility()) {
docsPanel.requestUpdate();
}

editor.doc = doc;
editor.doc.load();
editor.doc.resetHistory();
});

return editor;

function patchPageRootSpec(spec: ExtensionType[]) {
Expand All @@ -131,6 +176,7 @@ export async function mountDefaultDocEditor(collection: DocCollection) {
),
OverrideThemeExtension(themeExtension),
ParseDocUrlExtension(mockParseDocUrlService(collection)),
GenerateDocUrlExtension(mockGenerateDocUrlService(collection)),
NotificationExtension(mockNotificationService(editor)),
FontConfigExtension(CommunityCanvasTextFonts),
mockPeekViewExtension(attachmentViewerPanel),
Expand Down

0 comments on commit 8ecc20c

Please sign in to comment.