feat: improve test & bundler (#14434)

#### PR Dependency Tree


* **PR #14434** 👈

This tree was auto-generated by
[Charcoal](https://github.com/danerwilliams/charcoal)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Introduced rspack bundler as an alternative to webpack for optimized
builds.

* **Tests & Quality**
* Added comprehensive editor semantic tests covering markdown, hotkeys,
and slash-menu operations.
* Expanded CI cross-browser testing to Chromium, Firefox, and WebKit;
improved shape-rendering tests to account for zoom.

* **Bug Fixes**
  * Corrected CSS overlay styling for development servers.
  * Fixed TypeScript typings for build tooling.

* **Other**
  * Document duplication now produces consistent "(n)" suffixes.
  * French i18n completeness increased to 100%.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
DarkSky
2026-02-14 16:09:09 +08:00
committed by GitHub
parent 3bc28ba78c
commit 2b71b3f345
25 changed files with 1913 additions and 333 deletions
@@ -18,6 +18,7 @@ import type { DocPropertiesStore } from '../stores/doc-properties';
import type { DocsStore } from '../stores/docs';
import type { DocCreateOptions } from '../types';
import { DocService } from './doc';
import { getDuplicatedDocTitle } from './duplicate-title';
const logger = new DebugLogger('DocsService');
@@ -286,13 +287,7 @@ export class DocsService extends Service {
});
// duplicate doc title
const originalTitle = sourceDoc.title$.value;
const lastDigitRegex = /\((\d+)\)$/;
const match = originalTitle.match(lastDigitRegex);
const newNumber = match ? parseInt(match[1], 10) + 1 : 1;
const newPageTitle =
originalTitle.replace(lastDigitRegex, '') + `(${newNumber})`;
targetDoc.changeDocTitle(newPageTitle);
targetDoc.changeDocTitle(getDuplicatedDocTitle(sourceDoc.title$.value));
// duplicate doc properties
const properties = sourceDoc.getProperties();
@@ -0,0 +1,9 @@
const DUPLICATED_DOC_TITLE_SUFFIX = /\((\d+)\)$/;
export function getDuplicatedDocTitle(originalTitle: string) {
const match = originalTitle.match(DUPLICATED_DOC_TITLE_SUFFIX);
const nextSequence = match ? parseInt(match[1], 10) + 1 : 1;
return (
originalTitle.replace(DUPLICATED_DOC_TITLE_SUFFIX, '') + `(${nextSequence})`
);
}
@@ -46,6 +46,10 @@ import type {
} from '../../workspace';
import { WorkspaceImpl } from '../../workspace/impls/workspace';
import { getWorkspaceProfileWorker } from './out-worker';
import {
dedupeWorkspaceIds,
normalizeWorkspaceIds,
} from './workspace-id-utils';
export const LOCAL_WORKSPACE_LOCAL_STORAGE_KEY = 'affine-local-workspace';
export const LOCAL_WORKSPACE_GLOBAL_STATE_KEY =
@@ -61,13 +65,6 @@ type GlobalStateStorageLike = {
set<T>(key: string, value: T): void;
};
function normalizeWorkspaceIds(ids: unknown): string[] {
if (!Array.isArray(ids)) {
return [];
}
return ids.filter((id): id is string => typeof id === 'string');
}
function getElectronGlobalStateStorage(): GlobalStateStorageLike | null {
if (!BUILD_CONFIG.isElectron) {
return null;
@@ -113,7 +110,7 @@ export function setLocalWorkspaceIds(
? idsOrUpdater(getLocalWorkspaceIds())
: idsOrUpdater
);
const deduplicated = [...new Set(next)];
const deduplicated = dedupeWorkspaceIds(next);
const globalState = getElectronGlobalStateStorage();
if (globalState) {
@@ -168,14 +165,12 @@ class LocalWorkspaceFlavourProvider implements WorkspaceFlavourProvider {
}
setLocalWorkspaceIds(currentIds => {
return [
...new Set([
...currentIds,
...persistedIds,
...legacyIds,
...scannedIds,
]),
];
return dedupeWorkspaceIds([
...currentIds,
...persistedIds,
...legacyIds,
...scannedIds,
]);
});
})()
.catch(e => {
@@ -0,0 +1,8 @@
export function normalizeWorkspaceIds(ids: unknown): string[] {
if (!Array.isArray(ids)) return [];
return ids.filter((id): id is string => typeof id === 'string');
}
export function dedupeWorkspaceIds(ids: string[]): string[] {
return [...new Set(ids)];
}