mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-08-09 21:25:45 +08:00
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:
@@ -66,6 +66,7 @@ export function SharedDataTable<TData extends { id: string }, TValue>({
|
||||
setColumnFilters([]);
|
||||
}, [resetFiltersDeps]);
|
||||
|
||||
// eslint-disable-next-line react-hooks/incompatible-library
|
||||
const table = useReactTable({
|
||||
data,
|
||||
columns,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { FeatureType } from '@affine/graphql';
|
||||
import type { FeatureType } from '@affine/graphql';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import { Header } from '../header';
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useQuery } from '@affine/admin/use-query';
|
||||
import { FeatureType, listUsersQuery } from '@affine/graphql';
|
||||
import type { FeatureType } from '@affine/graphql';
|
||||
import { listUsersQuery } from '@affine/graphql';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
|
||||
export const useUserList = (filter?: {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Button } from '@affine/admin/components/ui/button';
|
||||
import { Input } from '@affine/admin/components/ui/input';
|
||||
import { AdminWorkspaceSort, FeatureType } from '@affine/graphql';
|
||||
import type { FeatureType } from '@affine/graphql';
|
||||
import { AdminWorkspaceSort } from '@affine/graphql';
|
||||
import type { Table } from '@tanstack/react-table';
|
||||
import {
|
||||
type ChangeEvent,
|
||||
|
||||
@@ -7,11 +7,11 @@ import { Input } from '@affine/admin/components/ui/input';
|
||||
import { Label } from '@affine/admin/components/ui/label';
|
||||
import { Separator } from '@affine/admin/components/ui/separator';
|
||||
import { Switch } from '@affine/admin/components/ui/switch';
|
||||
import type { FeatureType } from '@affine/graphql';
|
||||
import {
|
||||
adminUpdateWorkspaceMutation,
|
||||
adminWorkspaceQuery,
|
||||
adminWorkspacesQuery,
|
||||
FeatureType,
|
||||
} from '@affine/graphql';
|
||||
import { AccountIcon } from '@blocksuite/icons/rc';
|
||||
import { cssVarV2 } from '@toeverything/theme/v2';
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { AdminWorkspaceSort, FeatureType } from '@affine/graphql';
|
||||
import type { FeatureType } from '@affine/graphql';
|
||||
import { AdminWorkspaceSort } from '@affine/graphql';
|
||||
import { useState } from 'react';
|
||||
|
||||
import { Header } from '../header';
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { useQuery } from '@affine/admin/use-query';
|
||||
import type { AdminWorkspaceSort, FeatureType } from '@affine/graphql';
|
||||
import {
|
||||
adminWorkspacesCountQuery,
|
||||
AdminWorkspaceSort,
|
||||
adminWorkspacesQuery,
|
||||
FeatureType,
|
||||
} from '@affine/graphql';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
|
||||
@@ -27,7 +26,7 @@ export const useWorkspaceList = (filter?: {
|
||||
.join(',')}-${filter?.orderBy ?? ''}-${JSON.stringify(
|
||||
filter?.flags ?? {}
|
||||
)}`,
|
||||
[filter?.features, filter?.flags, filter?.keyword, filter?.orderBy]
|
||||
[filter]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -52,18 +51,7 @@ export const useWorkspaceList = (filter?: {
|
||||
enableDocEmbedding: filter?.flags?.enableDocEmbedding,
|
||||
},
|
||||
}),
|
||||
[
|
||||
filter?.features,
|
||||
filter?.flags?.enableAi,
|
||||
filter?.flags?.enableDocEmbedding,
|
||||
filter?.flags?.enableSharing,
|
||||
filter?.flags?.enableUrlPreview,
|
||||
filter?.flags?.public,
|
||||
filter?.keyword,
|
||||
filter?.orderBy,
|
||||
pagination.pageIndex,
|
||||
pagination.pageSize,
|
||||
]
|
||||
[filter, pagination.pageIndex, pagination.pageSize]
|
||||
);
|
||||
|
||||
const { data: listData, isValidating: isListValidating } = useQuery(
|
||||
|
||||
@@ -24,7 +24,7 @@ export function useDisposable<T extends Disposable | AsyncDisposable>(
|
||||
error: null,
|
||||
});
|
||||
|
||||
// oxlint-disable-next-line react-hooks/exhaustive-deps
|
||||
// oxlint-disable-next-line react/exhaustive-deps
|
||||
useEffect(() => {
|
||||
const abortController = new AbortController();
|
||||
let _data: T | null = null;
|
||||
@@ -54,7 +54,7 @@ export function useDisposable<T extends Disposable | AsyncDisposable>(
|
||||
}
|
||||
}
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
// oxlint-disable-next-line react/exhaustive-deps
|
||||
}, deps || []);
|
||||
|
||||
return state;
|
||||
|
||||
@@ -3,9 +3,6 @@
|
||||
*/
|
||||
import { useDebugValue, useEffect, useState } from 'react';
|
||||
|
||||
// internalRef is used as a reference and therefore save to be used inside an effect
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
|
||||
// the `process.env.NODE_ENV !== 'production'` condition is resolved by the build tool
|
||||
|
||||
const noop: (...args: any[]) => any = () => {};
|
||||
@@ -84,6 +81,7 @@ export const useRefEffect = <T>(
|
||||
}
|
||||
};
|
||||
}, // Keep a ref to the latest dependencies
|
||||
// oxlint-disable-next-line react/exhaustive-deps
|
||||
(internalRef.dependencies_ = dependencies)
|
||||
);
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
export * from './button';
|
||||
export * from './dropdown-button';
|
||||
export * from './icon-button';
|
||||
export * from './radio';
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
import type {
|
||||
RadioGroupItemProps,
|
||||
RadioGroupProps,
|
||||
} from '@radix-ui/react-radio-group';
|
||||
import * as RadixRadioGroup from '@radix-ui/react-radio-group';
|
||||
import clsx from 'clsx';
|
||||
import type { CSSProperties } from 'react';
|
||||
import { forwardRef } from 'react';
|
||||
|
||||
import { RadioGroup } from '../radio';
|
||||
import * as styles from './styles.css';
|
||||
|
||||
// for reference
|
||||
RadioGroup;
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* use {@link RadioGroup } instead
|
||||
*/
|
||||
export const RadioButton = forwardRef<
|
||||
HTMLButtonElement,
|
||||
RadioGroupItemProps & { spanStyle?: string }
|
||||
>(({ children, className, spanStyle, ...props }, ref) => {
|
||||
return (
|
||||
<RadixRadioGroup.Item
|
||||
ref={ref}
|
||||
{...props}
|
||||
className={clsx(styles.radioButton, className)}
|
||||
>
|
||||
<span className={clsx(styles.radioUncheckedButton, spanStyle)}>
|
||||
{children}
|
||||
</span>
|
||||
<RadixRadioGroup.Indicator
|
||||
className={clsx(styles.radioButtonContent, spanStyle)}
|
||||
>
|
||||
{children}
|
||||
</RadixRadioGroup.Indicator>
|
||||
</RadixRadioGroup.Item>
|
||||
);
|
||||
});
|
||||
RadioButton.displayName = 'RadioButton';
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* use {@link RadioGroup} instead
|
||||
*/
|
||||
export const RadioButtonGroup = forwardRef<
|
||||
HTMLDivElement,
|
||||
RadioGroupProps & { width?: CSSProperties['width'] }
|
||||
>(({ className, style, width, ...props }, ref) => {
|
||||
return (
|
||||
<RadixRadioGroup.Root
|
||||
ref={ref}
|
||||
className={clsx(styles.radioButtonGroup, className)}
|
||||
style={{ width, ...style }}
|
||||
{...props}
|
||||
></RadixRadioGroup.Root>
|
||||
);
|
||||
});
|
||||
RadioButtonGroup.displayName = 'RadioButtonGroup';
|
||||
@@ -1,5 +1,6 @@
|
||||
import { cssVar } from '@toeverything/theme';
|
||||
import { style } from '@vanilla-extract/css';
|
||||
|
||||
export const dropdownBtn = style({
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
@@ -30,6 +31,7 @@ export const dropdownBtn = style({
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const divider = style({
|
||||
width: '0.5px',
|
||||
height: '16px',
|
||||
@@ -38,6 +40,7 @@ export const divider = style({
|
||||
margin: '0 4px',
|
||||
marginRight: 0,
|
||||
});
|
||||
|
||||
export const dropdownWrapper = style({
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
@@ -47,6 +50,7 @@ export const dropdownWrapper = style({
|
||||
paddingLeft: '4px',
|
||||
paddingRight: '10px',
|
||||
});
|
||||
|
||||
export const dropdownIcon = style({
|
||||
borderRadius: '4px',
|
||||
selectors: {
|
||||
@@ -55,55 +59,3 @@ export const dropdownIcon = style({
|
||||
},
|
||||
},
|
||||
});
|
||||
export const radioButton = style({
|
||||
flexGrow: 1,
|
||||
flex: 1,
|
||||
selectors: {
|
||||
'&:not(:last-of-type)': {
|
||||
marginRight: '4px',
|
||||
},
|
||||
},
|
||||
});
|
||||
export const radioButtonContent = style({
|
||||
fontSize: cssVar('fontXs'),
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
height: '28px',
|
||||
padding: '4px 8px',
|
||||
borderRadius: '8px',
|
||||
filter: 'drop-shadow(0px 0px 4px rgba(0, 0, 0, 0.1))',
|
||||
whiteSpace: 'nowrap',
|
||||
userSelect: 'none',
|
||||
fontWeight: 600,
|
||||
selectors: {
|
||||
'&:hover': {
|
||||
background: cssVar('hoverColor'),
|
||||
},
|
||||
'&[data-state="checked"]': {
|
||||
background: cssVar('white'),
|
||||
},
|
||||
},
|
||||
});
|
||||
export const radioUncheckedButton = style([
|
||||
radioButtonContent,
|
||||
{
|
||||
color: cssVar('textSecondaryColor'),
|
||||
filter: 'none',
|
||||
selectors: {
|
||||
'[data-state="checked"] > &': {
|
||||
display: 'none',
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
export const radioButtonGroup = style({
|
||||
display: 'inline-flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
background: cssVar('hoverColorFilled'),
|
||||
borderRadius: '10px',
|
||||
padding: '2px',
|
||||
// @ts-expect-error - fix electron drag
|
||||
WebkitAppRegion: 'no-drag',
|
||||
});
|
||||
|
||||
@@ -46,7 +46,7 @@ export const DatePicker = (props: DatePickerProps) => {
|
||||
setCursor(dayjs(v));
|
||||
onChange?.(v);
|
||||
},
|
||||
[onChange]
|
||||
[setMode, onChange]
|
||||
);
|
||||
|
||||
const onCursorChange = useCallback(
|
||||
|
||||
@@ -83,7 +83,7 @@ export const useDraggable = <D extends DNDData = DNDData>(
|
||||
}
|
||||
: undefined,
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
// oxlint-disable-next-line react/exhaustive-deps
|
||||
}, [...deps, getOptions, context.toExternalData]);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -206,7 +206,7 @@ export const useDropTarget = <D extends DNDData = DNDData>(
|
||||
(dropTargetContext.fromExternalData as fromExternalData<D>))
|
||||
: undefined,
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
// oxlint-disable-next-line react/exhaustive-deps
|
||||
}, [...deps, getOptions, dropTargetContext.fromExternalData]);
|
||||
|
||||
const getDropTargetOptions = useCallback(() => {
|
||||
|
||||
@@ -94,7 +94,7 @@ export const useDndMonitor = <D extends DNDData = DNDData>(
|
||||
(dropTargetContext.fromExternalData as fromExternalData<D>))
|
||||
: undefined,
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
// oxlint-disable-next-line react/exhaustive-deps
|
||||
}, [...deps, getOptions, dropTargetContext.fromExternalData]);
|
||||
|
||||
const monitorOptions = useMemo(() => {
|
||||
|
||||
@@ -93,15 +93,15 @@ export const InlineEdit = ({
|
||||
const [editingValue, setEditingValue] = useState(value);
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
useImperativeHandle<InlineEditHandle, InlineEditHandle>(handleRef, () => ({
|
||||
triggerEdit,
|
||||
}));
|
||||
|
||||
const triggerEdit = useCallback(() => {
|
||||
if (!editable) return;
|
||||
setEditing(true);
|
||||
}, [editable]);
|
||||
|
||||
useImperativeHandle<InlineEditHandle, InlineEditHandle>(handleRef, () => ({
|
||||
triggerEdit,
|
||||
}));
|
||||
|
||||
const onDoubleClick = useCallback(() => {
|
||||
if (trigger !== 'doubleClick') return;
|
||||
triggerEdit();
|
||||
|
||||
@@ -69,7 +69,7 @@ export const RowInput = forwardRef<HTMLInputElement, RowInputProps>(
|
||||
if (!onBlur) return;
|
||||
selectRef.current?.addEventListener('blur', onBlur as any);
|
||||
return () => {
|
||||
// oxlint-disable-next-line react-hooks/exhaustive-deps
|
||||
// oxlint-disable-next-line react/exhaustive-deps
|
||||
selectRef.current?.removeEventListener('blur', onBlur as any);
|
||||
};
|
||||
}, [onBlur, selectRef]);
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
+4
-2
@@ -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]);
|
||||
|
||||
+3
-2
@@ -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);
|
||||
|
||||
+1
@@ -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'](),
|
||||
});
|
||||
|
||||
+1
@@ -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('');
|
||||
|
||||
+1
-1
@@ -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),
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -79,6 +79,6 @@ describe('tracker session signals', () => {
|
||||
const sessionStart = events.find(
|
||||
event => event.eventName === 'session_start'
|
||||
);
|
||||
expect((sessionStart?.params as any).session_number).toBe(2);
|
||||
expect(sessionStart?.params?.session_number).toBe(2);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -116,6 +116,7 @@ export function enableAutoTrack(root: HTMLElement, trackFn: TrackFn) {
|
||||
declare module 'react' {
|
||||
// we have to declare `T` but it's actually not used
|
||||
|
||||
// oxlint-disable-next-line no-unused-vars
|
||||
interface HTMLAttributes<T> {
|
||||
'data-event-props'?: EventsUnion;
|
||||
'data-event-arg'?: string;
|
||||
|
||||
@@ -94,8 +94,8 @@ export function setTelemetryContext(
|
||||
const nextUserProps = options.replaceUserProperties
|
||||
? (update.userProperties ?? {})
|
||||
: {
|
||||
...(context.userProperties ?? {}),
|
||||
...(update.userProperties ?? {}),
|
||||
...context.userProperties,
|
||||
...update.userProperties,
|
||||
};
|
||||
|
||||
context = {
|
||||
|
||||
@@ -241,7 +241,7 @@ function mergeSessionParams(
|
||||
engagementMs: number
|
||||
) {
|
||||
const merged: Record<string, unknown> = {
|
||||
...(params ?? {}),
|
||||
...params,
|
||||
};
|
||||
if (Number.isFinite(nextSessionId) && nextSessionId > 0) {
|
||||
merged.session_id = nextSessionId;
|
||||
|
||||
Reference in New Issue
Block a user