feat: bump eslint & oxlint (#14452)

#### PR Dependency Tree


* **PR #14452** 👈

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

* **Bug Fixes**
* Improved null-safety, dependency tracking, upload validation, and
error logging for more reliable uploads, clipboard, calendar linking,
telemetry, PDF/theme printing, and preview/zoom behavior.
* Tightened handling of all-day calendar events (missing date now
reported).

* **Deprecations**
  * Removed deprecated RadioButton and RadioButtonGroup; use RadioGroup.

* **Chores**
* Unified and upgraded linting/config, reorganized imports, and
standardized binary handling for more consistent builds and tooling.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
DarkSky
2026-02-16 13:52:08 +08:00
committed by GitHub
parent 792164edd1
commit 728e02cab7
156 changed files with 1230 additions and 1066 deletions
@@ -1,7 +1,6 @@
/**
* @vitest-environment happy-dom
*/
import '@blocksuite/affine-shared/test-utils';
import { getInternalStoreExtensions } from '@blocksuite/affine/extensions/store';
import { StoreExtensionManager } from '@blocksuite/affine-ext-loader';
@@ -3,7 +3,7 @@ import { PanTool } from '@blocksuite/affine-gfx-pointer';
import { on } from '@blocksuite/affine-shared/utils';
import type { PointerEventState } from '@blocksuite/std';
import {
BaseTool,
type BaseTool,
MouseButton,
type ToolOptionWithType,
type ToolType,
@@ -21,9 +21,7 @@ const pointerUpHandlers: unknown[] = [];
const pointerUpDisposers: Array<ReturnType<typeof vi.fn>> = [];
vi.mock('@blocksuite/affine-shared/utils', async () => {
const actual = await vi.importActual<
typeof import('@blocksuite/affine-shared/utils')
>('@blocksuite/affine-shared/utils');
const actual = await vi.importActual('@blocksuite/affine-shared/utils');
return {
...actual,
@@ -11,7 +11,7 @@ import type { BlockStdScope } from '@blocksuite/std';
import { css, html } from 'lit';
import { property } from 'lit/decorators.js';
import { getCustomPageEditorBlockSpecs } from '../text-renderer';
import { getCustomPageEditorBlockSpecs } from '../page-editor-block-specs';
import { ArtifactTool } from './artifact-tool';
import type { ToolError } from './type';
@@ -0,0 +1,22 @@
import { ViewExtensionManager } from '@blocksuite/affine/ext-loader';
import { getInternalViewExtensions } from '@blocksuite/affine/extensions/view';
import { BlockViewIdentifier } from '@blocksuite/affine/std';
import type { ExtensionType } from '@blocksuite/affine/store';
import { literal } from 'lit/static-html.js';
const manager = new ViewExtensionManager([...getInternalViewExtensions()]);
const customPageEditorBlockSpecs: ExtensionType[] = [
...manager.get('page'),
{
setup: di => {
di.override(
BlockViewIdentifier('affine:page'),
() => literal`affine-page-root`
);
},
},
];
export const getCustomPageEditorBlockSpecs = () => {
return customPageEditorBlockSpecs;
};
@@ -1,5 +1,4 @@
import { createReactComponentFromLit } from '@affine/component';
import { getViewManager } from '@affine/core/blocksuite/manager/view';
import type { FeatureFlagService } from '@affine/core/modules/feature-flag';
import { PeekViewProvider } from '@blocksuite/affine/components/peek';
import { SignalWatcher, WithDisposable } from '@blocksuite/affine/global/lit';
@@ -13,7 +12,6 @@ import {
import { unsafeCSSVarV2 } from '@blocksuite/affine/shared/theme';
import {
BlockStdScope,
BlockViewIdentifier,
type EditorHost,
ShadowlessElement,
} from '@blocksuite/affine/std';
@@ -32,27 +30,12 @@ import { css, html, nothing, type PropertyValues, unsafeCSS } from 'lit';
import { property, query } from 'lit/decorators.js';
import { classMap } from 'lit/directives/class-map.js';
import { keyed } from 'lit/directives/keyed.js';
import { literal } from 'lit/static-html.js';
import React from 'react';
import { filter } from 'rxjs/operators';
import { markDownToDoc } from '../../utils';
import type { AffineAIPanelState } from '../widgets/ai-panel/type';
export const getCustomPageEditorBlockSpecs: () => ExtensionType[] = () => {
const manager = getViewManager().config.init().value;
return [
...manager.get('page'),
{
setup: di => {
di.override(
BlockViewIdentifier('affine:page'),
() => literal`affine-page-root`
);
},
},
];
};
import { getCustomPageEditorBlockSpecs } from './page-editor-block-specs';
const customHeadingStyles = css`
.custom-heading {
@@ -1,4 +1,7 @@
import { ViewBody, ViewHeader } from '@affine/core/modules/workbench';
import {
ViewBody,
ViewHeader,
} from '@affine/core/modules/workbench/view/view-islands';
import { AttachmentFallback, AttachmentPreviewErrorBoundary } from './error';
import { PDFViewer } from './pdf/pdf-viewer';
@@ -280,6 +280,7 @@ const BlockSuiteEditorImpl = ({
export const BlockSuiteEditor = (props: EditorProps) => {
const [isLoading, setIsLoading] = useState(true);
const [longerLoading, setLongerLoading] = useState(false);
// eslint-disable-next-line react-hooks/purity
const [loadStartTime] = useState(Date.now());
const workspaceService = useService(WorkspaceService);
@@ -1,7 +1,7 @@
import { Popover, uniReactRoot } from '@affine/component';
import { Button } from '@affine/component/ui/button';
import { Menu, MenuItem } from '@affine/component/ui/menu';
import { PeekViewService } from '@affine/core/modules/peek-view';
import { PeekViewService } from '@affine/core/modules/peek-view/services/peek-view';
import {
type Cell,
type CellRenderProps,
@@ -361,8 +361,10 @@ const FileCellComponent: ForwardRefRenderFunction<
CellRenderProps<{}, FileCellRawValueType, FileCellJsonValueType>
> = (props, ref): ReactNode => {
const peekView = useService(PeekViewService);
// eslint-disable-next-line react-hooks/exhaustive-deps
const manager = useMemo(() => new FileCellManager(props, peekView), []);
const manager = useMemo(
() => new FileCellManager(props, peekView), // eslint-disable-line react-hooks/preserve-manual-memoization
[] // oxlint-disable-line react/exhaustive-deps
);
useEffect(() => {
return () => {
@@ -260,8 +260,10 @@ export const MemberPreview = ({
export const MultiMemberSelect: React.FC<MemberManagerOptions> = props => {
const inputRef = useRef<HTMLInputElement>(null);
const memberListRef = useRef<HTMLDivElement>(null);
// eslint-disable-next-line react-hooks/exhaustive-deps
const memberManager = useMemo(() => new MemberManager(props), []);
const memberManager = useMemo(
() => new MemberManager(props), // eslint-disable-line react-hooks/preserve-manual-memoization
[] // oxlint-disable-line react/exhaustive-deps
);
const isLoading = useSignalValue(memberManager.userListService.isLoading$);
const selectedMembers = useSignalValue(memberManager.selectedMembers);
@@ -69,8 +69,10 @@ const MemberCellComponent: ForwardRefRenderFunction<
DataViewCellLifeCycle,
CellRenderProps<{}, MemberCellRawValueType, MemberCellJsonValueType>
> = (props, ref): ReactNode => {
// eslint-disable-next-line react-hooks/exhaustive-deps
const manager = useMemo(() => new MemberManager(props), []);
const manager = useMemo(
() => new MemberManager(props), // eslint-disable-line react-hooks/preserve-manual-memoization
[] // oxlint-disable-line react/exhaustive-deps
);
useImperativeHandle(
ref,
@@ -14,17 +14,15 @@ export const useGuard = <
) => {
const guardService = useService(GuardService);
useEffect(() => {
// oxlint-disable-next-line exhaustive-deps
guardService.revalidateCan(action, ...args);
// eslint-disable-next-line react-hooks/exhaustive-deps
// oxlint-disable-next-line react/exhaustive-deps
}, [action, guardService, ...args]);
const livedata$ = useMemo(
() => {
// oxlint-disable-next-line exhaustive-deps
return guardService.can$(action, ...args);
},
// eslint-disable-next-line react-hooks/exhaustive-deps
// oxlint-disable-next-line react/exhaustive-deps
[action, guardService, ...args]
);
@@ -22,9 +22,9 @@ export function useAsyncCallback<T extends any[]>(
const handleAsyncError = React.useContext(AsyncCallbackContext);
return React.useCallback(
(...args: any) => {
// oxlint-disable-next-line exhaustive-deps
// oxlint-disable-next-line react/exhaustive-deps
callback(...args).catch(e => handleAsyncError(e));
},
[...deps] // eslint-disable-line react-hooks/exhaustive-deps
[...deps] // oxlint-disable-line react/exhaustive-deps
);
}
@@ -9,12 +9,9 @@ export const useCatchEventCallback = <
cb: (e: E, ...args: Args) => void | Promise<void>,
deps: DependencyList
) => {
return useAsyncCallback(
async (e: E, ...args: Args) => {
e.stopPropagation();
await cb(e, ...args);
},
// eslint-disable-next-line react-hooks/exhaustive-deps
deps
);
return useAsyncCallback(async (e: E, ...args: Args) => {
e.stopPropagation();
await cb(e, ...args);
// oxlint-disable-next-line react/exhaustive-deps
}, deps);
};
@@ -89,7 +89,7 @@ const PageOperationCellMenuItem = ({
track.$.docInfoPanel.$.open();
workspaceDialogService.open('doc-info', { docId: blocksuiteDoc.id });
}
}, [blocksuiteDoc?.id, workspaceDialogService]);
}, [blocksuiteDoc, workspaceDialogService]);
const onDisablePublicSharing = useCallback(() => {
// TODO(@EYHN): implement disable public sharing
@@ -68,9 +68,7 @@ const DesktopTagEditMenu = ({
<MenuItem
prefixIcon={<DeleteIcon />}
type="danger"
onClick={() => {
tag?.id ? onTagDelete(tag.id) : null;
}}
onClick={() => onTagDelete(tag.id)}
>
{t['Delete']()}
</MenuItem>
@@ -203,9 +201,7 @@ const MobileTagEditMenu = ({
<ConfigModal.RowGroup>
<ConfigModal.Row
className={styles.mobileTagEditDeleteRow}
onClick={() => {
onTagDelete(tag.id);
}}
onClick={() => onTagDelete(tag.id)}
>
<DeleteIcon />
{t['Delete']()}
@@ -150,6 +150,7 @@ export const TagsEditor = ({
const idx = tagColors.findIndex(c => c.value === color);
return tagColors[(idx + 1) % tagColors.length].value;
},
// eslint-disable-next-line react-hooks/purity
tagColors[Math.floor(Math.random() * tagColors.length)].value
);
@@ -41,7 +41,7 @@ const DesktopTextValue = ({
useEffect(() => {
ref.current?.addEventListener('blur', handleBlur);
return () => {
// eslint-disable-next-line react-hooks/exhaustive-deps
// oxlint-disable-next-line react/exhaustive-deps
ref.current?.removeEventListener('blur', handleBlur);
};
}, [handleBlur]);
@@ -108,7 +108,7 @@ const MobileTextValue = ({
useEffect(() => {
ref.current?.addEventListener('blur', handleBlur);
return () => {
// eslint-disable-next-line react-hooks/exhaustive-deps
// oxlint-disable-next-line react/exhaustive-deps
ref.current?.removeEventListener('blur', handleBlur);
};
}, [handleBlur]);
@@ -157,14 +157,13 @@ const CalDAVLinkDialog = ({
setErrors(nextErrors);
return;
}
setSubmitting(true);
try {
await gqlService.gql({
query: linkCalDavAccountMutation,
variables: {
input: {
providerPresetId: selectedProvider!.id,
providerPresetId: selectedProvider.id,
username: username.trim(),
password,
displayName: displayName.trim() || null,
@@ -416,6 +415,7 @@ export const IntegrationsPanel = () => {
urlService.openExternal(data.linkCalendarAccount);
setOpenedExternalWindow(true);
} catch (error) {
console.error('Failed to link calendar account', error);
notify.error({
title: t['com.affine.integration.calendar.auth.start-error'](),
});
@@ -456,6 +456,7 @@ export const IntegrationsPanel = () => {
}
);
} catch (error) {
console.error('Failed to unlink calendar account', error);
notify.error({
title: t['com.affine.integration.calendar.account.unlink-error'](),
});
@@ -15,6 +15,7 @@ import { Avatar } from '@affine/component/ui/avatar';
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
import { useNavigateHelper } from '@affine/core/components/hooks/use-navigate-helper';
import { BackupService } from '@affine/core/modules/backup/services';
import { toArrayBuffer } from '@affine/core/utils/array-buffer';
import { i18nTime, useI18n } from '@affine/i18n';
import track from '@affine/track';
import {
@@ -47,7 +48,7 @@ const BlobAvatar = ({
const [url, setUrl] = useState<string | null>(null);
useEffect(() => {
if (!blob) return;
const url = URL.createObjectURL(new Blob([blob]));
const url = URL.createObjectURL(new Blob([toArrayBuffer(blob)]));
setUrl(url);
return () => {
URL.revokeObjectURL(url);
@@ -75,6 +75,7 @@ export const CalendarSettingPanel = () => {
}));
await calendar.updateWorkspaceCalendars(items);
} catch (error) {
console.error('Failed to save calendar settings', error);
notify.error({
title: t['com.affine.integration.calendar.save-error'](),
});
@@ -73,6 +73,7 @@ export const SelfHostTeamCard = () => {
license?.expiredAt || 0
).toLocaleDateString(),
leftDays: Math.floor(
// eslint-disable-next-line react-hooks/purity
(new Date(license?.expiredAt || 0).getTime() - Date.now()) /
(1000 * 60 * 60 * 24)
).toLocaleString(),
@@ -104,7 +104,7 @@ export const Component = () => {
const [searchParams] = useSearchParams();
const [message, setMessage] = useState('');
const [error, setError] = useState('');
const [retryKey, setRetryKey] = useState(0);
const [retryCount, setRetryCount] = useState(0);
const { jumpToSignIn, jumpToIndex } = useNavigateHelper();
const idempotencyKey = useMemo(() => nanoid(), []);
@@ -115,9 +115,10 @@ export const Component = () => {
const call = effect(
switchMap(() => {
return fromPromise(async signal => {
retryKey;
// TODO(@eyhn): i18n
setMessage('Checking account status...');
setMessage(
`Checking account status...${retryCount > 0 ? ` (retry ${retryCount})` : ''}`
);
setError('');
await authService.session.waitForRevalidation(signal);
const loggedIn =
@@ -179,7 +180,7 @@ export const Component = () => {
plan,
jumpToIndex,
recurring,
retryKey,
retryCount,
variant,
coupon,
urlService,
@@ -197,7 +198,7 @@ export const Component = () => {
<>
{error}
<br />
<Button variant="primary" onClick={() => setRetryKey(i => i + 1)}>
<Button variant="primary" onClick={() => setRetryCount(i => i + 1)}>
Retry
</Button>
</>
@@ -5,8 +5,8 @@ import {
type ChatContextValue,
} from '@affine/core/blocksuite/ai/components/ai-chat-content';
import type { ChatStatus } from '@affine/core/blocksuite/ai/components/ai-chat-messages';
import type { AIChatToolbar } from '@affine/core/blocksuite/ai/components/ai-chat-toolbar';
import {
AIChatToolbar,
configureAIChatToolbar,
getOrCreateAIChatToolbar,
} from '@affine/core/blocksuite/ai/components/ai-chat-toolbar';
@@ -6,8 +6,8 @@ import {
type ChatContextValue,
} from '@affine/core/blocksuite/ai/components/ai-chat-content';
import type { ChatStatus } from '@affine/core/blocksuite/ai/components/ai-chat-messages';
import type { AIChatToolbar } from '@affine/core/blocksuite/ai/components/ai-chat-toolbar';
import {
AIChatToolbar,
configureAIChatToolbar,
getOrCreateAIChatToolbar,
} from '@affine/core/blocksuite/ai/components/ai-chat-toolbar';
@@ -253,21 +253,19 @@ const WorkspacePage = ({ meta }: { meta: WorkspaceMetadata }) => {
};
}, [meta, workspacesService]);
const isRootDocReady =
useLiveData(
useMemo(
() =>
workspace
? LiveData.from(
workspace.engine.doc
.docState$(workspace.id)
.pipe(map(v => v.ready)),
false
)
: null,
[workspace]
)
) ?? false;
const rootDocReady$ = useMemo(
() =>
workspace
? LiveData.from(
workspace.engine.doc
.docState$(workspace.id)
.pipe(map(v => v.ready)),
false
)
: null,
[workspace]
);
const isRootDocReady = useLiveData(rootDocReady$) ?? false;
useEffect(() => {
if (workspace) {
@@ -109,21 +109,19 @@ export const WorkspaceLayout = ({
workspaceServer,
]);
const isRootDocReady =
useLiveData(
useMemo(
() =>
workspace
? LiveData.from(
workspace.engine.doc
.docState$(workspace.id)
.pipe(map(v => v.ready)),
false
)
: null,
[workspace]
)
) ?? false;
const rootDocReady$ = useMemo(
() =>
workspace
? LiveData.from(
workspace.engine.doc
.docState$(workspace.id)
.pipe(map(v => v.ready)),
false
)
: null,
[workspace]
);
const isRootDocReady = useLiveData(rootDocReady$) ?? false;
if (!workspace) {
return null; // skip this, workspace will be set in layout effect
@@ -1,4 +1,5 @@
import { getStoreManager } from '@affine/core/blocksuite/manager/store';
import { toArrayBuffer } from '@affine/core/utils/array-buffer';
import { Container } from '@blocksuite/affine/global/di';
import {
customImageProxyMiddleware,
@@ -46,7 +47,9 @@ export class SnapshotHelper extends Service {
get: async key => {
const record =
await this.workspaceService.workspace.engine.blob.get(key);
return record ? new Blob([record.data], { type: record.mime }) : null;
return record
? new Blob([toArrayBuffer(record.data)], { type: record.mime })
: null;
},
set() {
return Promise.resolve('');
@@ -112,7 +112,7 @@ const DatabaseBacklinkRow = ({
useMemo(
() =>
row?.docId ? templateDocService.list.isTemplate$(row.docId) : undefined,
[row?.docId, templateDocService.list]
[row, templateDocService.list]
)
);
@@ -9,7 +9,8 @@ export { DocsService } from './services/docs';
import type { Framework } from '@toeverything/infra';
import { WorkspaceDBService } from '../db/services/db';
import { WorkspaceScope, WorkspaceService } from '../workspace';
import { WorkspaceScope } from '../workspace/scopes/workspace';
import { WorkspaceService } from '../workspace/services/workspace';
import { Doc } from './entities/doc';
import { DocRecord } from './entities/record';
import { DocRecordList } from './entities/record-list';
@@ -8,7 +8,7 @@ import { ObjectPool, Service } from '@toeverything/infra';
import { combineLatest, map } from 'rxjs';
import { initDocFromProps } from '../../../blocksuite/initialization';
import { getAFFiNEWorkspaceSchema } from '../../workspace';
import { getAFFiNEWorkspaceSchema } from '../../workspace/global-schema';
import type { Doc } from '../entities/doc';
import { DocRecordList } from '../entities/record-list';
import { DocCreated, DocInitialized } from '../events';
@@ -1,3 +1,4 @@
import { toArrayBuffer } from '@affine/core/utils/array-buffer';
import type { DocMode } from '@blocksuite/affine/model';
import { ZipTransformer } from '@blocksuite/affine/widgets/linked-doc';
import { Service } from '@toeverything/infra';
@@ -27,7 +28,7 @@ export class ImportTemplateService extends Service {
const [importedDoc] = await ZipTransformer.importDocs(
workspace.docCollection,
getAFFiNEWorkspaceSchema(),
new Blob([docBinary], {
new Blob([toArrayBuffer(docBinary)], {
type: 'application/zip',
})
);
@@ -234,7 +234,7 @@ export class ReadwiseIntegration extends Entity<{ writer: IntegrationWriter }> {
async deleteAll() {
const refs = await this.getRefs();
await Promise.all(
refs.map(ref => {
refs.map(async ref => {
const doc = this.docsService.list.doc$(ref.id).value;
if (doc) {
doc.moveToTrash();
@@ -3,6 +3,7 @@ import {
type TranscriptionBlockModel,
} from '@affine/core/blocksuite/ai/blocks/transcription-block/model';
import { insertFromMarkdown } from '@affine/core/blocksuite/utils';
import { toArrayBuffer } from '@affine/core/utils/array-buffer';
import { encodeAudioBlobToOpusSlices } from '@affine/core/utils/opus-encoding';
import { DebugLogger } from '@affine/debug';
import { AiJobStatus } from '@affine/graphql';
@@ -137,7 +138,7 @@ export class AudioAttachmentBlock extends Entity<AttachmentBlockModel> {
}
const slices = await encodeAudioBlobToOpusSlices(buffer, 64000);
const files = slices.map((slice, index) => {
const blob = new Blob([slice], { type: 'audio/opus' });
const blob = new Blob([toArrayBuffer(slice)], { type: 'audio/opus' });
return new File([blob], this.props.props.name + `-${index}.opus`, {
type: 'audio/opus',
});
@@ -1,3 +1,4 @@
import { toArrayBuffer } from '@affine/core/utils/array-buffer';
import { DebugLogger } from '@affine/debug';
import {
catchErrorInto,
@@ -170,7 +171,7 @@ export class AudioMedia extends Entity<AudioSource> {
private async loadAudioBuffer() {
const uint8Array = await this.getBuffer();
return new Blob([uint8Array]);
return new Blob([toArrayBuffer(uint8Array)]);
}
readonly revalidateBuffer = effect(
@@ -1,3 +1,4 @@
import { toArrayBuffer } from '@affine/core/utils/array-buffer';
import { Entity, LiveData, ObjectPool } from '@toeverything/infra';
import { catchError, from, map, of, startWith, switchMap } from 'rxjs';
@@ -41,7 +42,7 @@ export class PDF extends Entity<{ blobId: string }> {
.then(blobRecord => {
if (blobRecord) {
const { data, mime: type } = blobRecord;
const blob = new Blob([data], { type });
const blob = new Blob([toArrayBuffer(data)], { type });
return blob.arrayBuffer();
}
@@ -16,7 +16,7 @@ import { firstValueFrom, map, race } from 'rxjs';
import type { AIChatBlockModel } from '../../../blocksuite/ai/blocks';
import { resolveLinkToDoc } from '../../navigation';
import type { WorkbenchService } from '../../workbench';
import type { WorkbenchService } from '../../workbench/services/workbench';
import type { ImagePreviewData } from '../view/image-preview';
export type DocReferenceInfo = {
@@ -1,6 +1,6 @@
import { type Framework } from '@toeverything/infra';
import { WorkbenchService } from '../workbench';
import { WorkbenchService } from '../workbench/services/workbench';
import { WorkspaceScope } from '../workspace';
import { PeekViewEntity } from './entities/peek-view';
import { PeekViewService } from './services/peek-view';
@@ -22,7 +22,7 @@ import clsx from 'clsx';
import { lazy, Suspense, useCallback, useEffect } from 'react';
import type { Subscription } from 'rxjs';
import { WorkbenchService } from '../../../workbench';
import { WorkbenchService } from '../../../workbench/services/workbench';
import type { DocReferenceInfo } from '../../entities/peek-view';
import { PeekViewService } from '../../services/peek-view';
import { useEditor } from '../utils';
@@ -153,7 +153,6 @@ export const PeekViewModalContainer = forwardRef<
!target.getBoundingClientRect().width &&
iteration < 10
) {
// eslint-disable-next-line react-hooks/exhaustive-deps
target = target.parentElement || undefined;
iteration++;
}
@@ -27,7 +27,7 @@ import { ServerService } from '../../cloud';
import { WorkspaceDialogService } from '../../dialogs';
import { DocsService } from '../../doc/services/docs';
import { toDocSearchParams } from '../../navigation';
import { WorkbenchService } from '../../workbench';
import { WorkbenchService } from '../../workbench/services/workbench';
import type {
AttachmentPeekViewInfo,
DocReferenceInfo,
@@ -14,7 +14,7 @@ export {
import { type Framework } from '@toeverything/infra';
import { WorkspaceServerService } from '../cloud';
import { WorkspaceServerService } from '../cloud/services/workspace-server';
import { DocScope, DocService } from '../doc';
import {
WorkspaceLocalState,
@@ -1,3 +1,4 @@
import { toArrayBuffer } from '@affine/core/utils/array-buffer';
import { DebugLogger } from '@affine/debug';
import {
createWorkspaceMutation,
@@ -192,7 +193,9 @@ class CloudWorkspaceFlavourProvider implements WorkspaceFlavourProvider {
blobSource: {
get: async key => {
const record = await blobStorage.get(key);
return record ? new Blob([record.data], { type: record.mime }) : null;
return record
? new Blob([toArrayBuffer(record.data)], { type: record.mime })
: null;
},
delete: async () => {
return;
@@ -391,7 +394,9 @@ class CloudWorkspaceFlavourProvider implements WorkspaceFlavourProvider {
storage.connection.disconnect();
if (localBlob) {
return new Blob([localBlob.data], { type: localBlob.mime });
return new Blob([toArrayBuffer(localBlob.data)], {
type: localBlob.mime,
});
}
const cloudBlob = await new CloudBlobStorage({
@@ -401,7 +406,7 @@ class CloudWorkspaceFlavourProvider implements WorkspaceFlavourProvider {
if (!cloudBlob) {
return null;
}
return new Blob([cloudBlob.data], { type: cloudBlob.mime });
return new Blob([toArrayBuffer(cloudBlob.data)], { type: cloudBlob.mime });
}
async listBlobs(id: string): Promise<ListedBlobRecord[]> {
@@ -1,3 +1,4 @@
import { toArrayBuffer } from '@affine/core/utils/array-buffer';
import { DebugLogger } from '@affine/debug';
import {
type BlobStorage,
@@ -266,7 +267,9 @@ class LocalWorkspaceFlavourProvider implements WorkspaceFlavourProvider {
blobSource: {
get: async key => {
const record = await blobStorage.get(key);
return record ? new Blob([record.data], { type: record.mime }) : null;
return record
? new Blob([toArrayBuffer(record.data)], { type: record.mime })
: null;
},
delete: async () => {
return;
@@ -394,7 +397,9 @@ class LocalWorkspaceFlavourProvider implements WorkspaceFlavourProvider {
storage.connection.connect();
await storage.connection.waitForConnected();
const blob = await storage.get(blobKey);
return blob ? new Blob([blob.data], { type: blob.mime }) : null;
return blob
? new Blob([toArrayBuffer(blob.data)], { type: blob.mime })
: null;
}
async listBlobs(id: string): Promise<ListedBlobRecord[]> {
@@ -1,3 +1,4 @@
import { toArrayBuffer } from '@affine/core/utils/array-buffer';
import isSvg from 'is-svg';
function fastCheckIsNotSvg(buffer: Uint8Array) {
@@ -49,11 +50,10 @@ export function isSvgBuffer(buffer: Uint8Array) {
}
export function bufferToBlob(buffer: Uint8Array | ArrayBuffer) {
const isSVG = isSvgBuffer(
buffer instanceof ArrayBuffer ? new Uint8Array(buffer) : buffer
);
const arrayBuffer = toArrayBuffer(buffer);
const isSVG = isSvgBuffer(new Uint8Array(arrayBuffer));
// for svg blob, we need to explicitly set the type to image/svg+xml
return isSVG
? new Blob([buffer], { type: 'image/svg+xml' })
: new Blob([buffer]);
? new Blob([arrayBuffer], { type: 'image/svg+xml' })
: new Blob([arrayBuffer]);
}
@@ -1,10 +1,11 @@
import type { FeatureFlagService } from '@affine/core/modules/feature-flag';
import { toArrayBuffer } from '@affine/core/utils/array-buffer';
import type { Workspace as WorkspaceInterface } from '@blocksuite/affine/store';
import { Entity, LiveData, yjsGetPath } from '@toeverything/infra';
import type { Observable } from 'rxjs';
import { Doc as YDoc, transact } from 'yjs';
import { DocsService } from '../../doc';
import { DocsService } from '../../doc/services/docs';
import { WorkspaceImpl } from '../impls/workspace';
import type { WorkspaceScope } from '../scopes/workspace';
import { WorkspaceEngineService } from '../services/engine';
@@ -39,7 +40,7 @@ export class Workspace extends Entity {
get: async key => {
const record = await this.engine.blob.get(key);
return record
? new Blob([record.data], { type: record.mime })
? new Blob([toArrayBuffer(record.data)], { type: record.mime })
: null;
},
delete: async () => {
@@ -0,0 +1,33 @@
/**
* Convert binary data to a strict ArrayBuffer for DOM APIs whose types
* require ArrayBuffer-backed views (not ArrayBufferLike).
*/
export function toArrayBuffer(
data: ArrayBuffer | ArrayBufferLike | ArrayBufferView
): ArrayBuffer {
if (data instanceof ArrayBuffer) {
return data;
}
if (ArrayBuffer.isView(data)) {
if (data.buffer instanceof ArrayBuffer) {
if (data.byteOffset === 0 && data.byteLength === data.buffer.byteLength) {
return data.buffer;
}
return data.buffer.slice(
data.byteOffset,
data.byteOffset + data.byteLength
);
}
const bytes = new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
const copy = new Uint8Array(bytes.byteLength);
copy.set(bytes);
return copy.buffer;
}
const bytes = new Uint8Array(data);
const copy = new Uint8Array(bytes.byteLength);
copy.set(bytes);
return copy.buffer;
}
@@ -3,6 +3,7 @@ import { apis } from '@affine/electron-api';
import { ArrayBufferTarget, Muxer } from 'mp4-muxer';
import { isLink } from '../modules/navigation/utils';
import { toArrayBuffer } from './array-buffer';
interface AudioEncodingConfig {
sampleRate: number;
@@ -38,16 +39,6 @@ async function blobToArrayBuffer(
return toArrayBuffer(blob);
}
function toArrayBuffer(data: ArrayBuffer | ArrayBufferView): ArrayBuffer {
if (data instanceof ArrayBuffer) {
return data;
}
return data.buffer.slice(
data.byteOffset,
data.byteOffset + data.byteLength
) as ArrayBuffer;
}
function getRecordingFileUrl(filepath: string): URL {
const base =
typeof location !== 'undefined' && location.protocol === 'assets:'
@@ -175,7 +166,7 @@ async function encodeAudioFrames({
numberOfFrames: chunk.length / numberOfChannels,
numberOfChannels,
timestamp: (offset * 1000000) / sampleRate,
data: chunk,
data: toArrayBuffer(chunk),
});
encoder.encode(frame);
@@ -448,7 +439,7 @@ export const createStreamEncoder = (
numberOfFrames:
buffer.length / BYTES_PER_SAMPLE / codecs.numberOfChannels,
timestamp: 0,
data: buffer,
data: toArrayBuffer(buffer),
});
};