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 -1
View File
@@ -31,7 +31,7 @@ assert.strictEqual(
bench
.add('tiktoken', () => {
const encoder = encoding_for_model('gpt-4o');
encoder.encode_ordinary(FIXTURE).length;
void encoder.encode_ordinary(FIXTURE).length;
})
.add('native', () => {
fromModelName('gpt-4o').count(FIXTURE);
@@ -43,7 +43,6 @@ class MockR2Provider extends R2StorageProvider {
destroy() {}
// @ts-ignore expect override
override async proxyPutObject(
key: string,
body: any,
@@ -1,6 +1,7 @@
import { LookupAddress } from 'node:dns';
import type { ExecutionContext, TestFn } from 'ava';
import ava from 'ava';
import { LookupAddress } from 'dns';
import Sinon from 'sinon';
import type { Response } from 'supertest';
@@ -14,7 +15,6 @@ import { createTestingApp, TestingApp } from './utils';
type TestContext = {
app: TestingApp;
};
const test = ava as TestFn<TestContext>;
const LookupAddressStub = (async (_hostname, options) => {
@@ -51,10 +51,10 @@ function parseKey(privateKey: string) {
let priv: KeyObject;
try {
priv = createPrivateKey({ key: keyBuf, format: 'pem', type: 'pkcs8' });
} catch (e1) {
} catch {
try {
priv = createPrivateKey({ key: keyBuf, format: 'pem', type: 'sec1' });
} catch (e2) {
} catch {
// As a last resort rely on auto-detection
priv = createPrivateKey(keyBuf);
}
@@ -22,12 +22,14 @@ function firstNonEmpty(...values: Array<string | undefined>) {
}
export function getRequestClientIp(req: Request) {
return firstNonEmpty(
req.get('CF-Connecting-IP'),
firstForwardedForIp(req.get('X-Forwarded-For')),
req.get('X-Real-IP'),
req.ip
)!;
return (
firstNonEmpty(
req.get('CF-Connecting-IP'),
firstForwardedForIp(req.get('X-Forwarded-For')),
req.get('X-Real-IP'),
req.ip
) ?? ''
);
}
export function getRequestTrackerId(req: Request) {
@@ -39,6 +41,7 @@ export function getRequestTrackerId(req: Request) {
req.get('X-Real-IP'),
req.get('CF-Ray'),
req.ip
)!
) ??
''
);
}
@@ -180,7 +180,7 @@ export async function assertSsrFSafeUrl(
let addresses: string[];
try {
addresses = await resolveHostAddresses(hostname);
} catch (error) {
} catch {
throw createSsrfBlockedError('unresolvable_hostname', {
url: url.toString(),
hostname,
@@ -44,11 +44,11 @@ const staticPaths = new Set([
'trash',
]);
const markdownType = [
const markdownType = new Set([
'text/markdown',
'application/markdown',
'text/x-markdown',
];
]);
@Controller('/workspace')
export class DocRendererController {
@@ -109,7 +109,7 @@ export class DocRendererController {
if (
isDocPath &&
req.accepts().some(t => markdownType.includes(t.toLowerCase()))
req.accepts().some(t => markdownType.has(t.toLowerCase()))
) {
try {
const allowPreview = await this.allowDocPreview(workspaceId, sub);
@@ -56,7 +56,7 @@ defineModuleConfig('mailer', {
env: 'MAILER_PASSWORD',
},
'SMTP.sender': {
desc: 'Sender of all the emails (e.g. "AFFiNE Self Hosted \<noreply@example.com\>")',
desc: 'Sender of all the emails (e.g. "AFFiNE Self Hosted <noreply@example.com>")',
default: 'AFFiNE Self Hosted <noreply@example.com>',
env: 'MAILER_SENDER',
},
@@ -92,7 +92,7 @@ defineModuleConfig('mailer', {
default: '',
},
'fallbackSMTP.sender': {
desc: 'Sender of all the emails (e.g. "AFFiNE Self Hosted \<noreply@example.com\>")',
desc: 'Sender of all the emails (e.g. "AFFiNE Self Hosted <noreply@example.com>")',
default: '',
},
'fallbackSMTP.ignoreTLS': {
@@ -2,9 +2,11 @@ import { Body, Controller, Options, Post, Req, Res } from '@nestjs/common';
import type { Request, Response } from 'express';
import { BadRequest, Throttle, UseNamedGuard } from '../../base';
import type { CurrentUser as CurrentUserType } from '../auth';
import { Public } from '../auth';
import { CurrentUser } from '../auth';
import {
CurrentUser,
type CurrentUser as CurrentUserType,
Public,
} from '../auth';
import { TelemetryService } from './service';
import { TelemetryAck, type TelemetryBatch } from './types';
@@ -110,10 +110,10 @@ export class CalendarAccountModel extends BaseModel {
refreshIntervalMinutes: data.refreshIntervalMinutes,
};
if (!!accessToken) {
if (accessToken) {
updateData.accessToken = accessToken;
}
if (!!refreshToken) {
if (refreshToken) {
updateData.refreshToken = refreshToken;
}
@@ -117,7 +117,7 @@ export class CopilotSessionModel extends BaseModel {
if (typeof value !== 'string') {
return value;
}
return value.replace(/\u0000/g, '') as T;
return value.replaceAll('\0', '') as T;
}
private sanitizeJsonValue<T>(value: T): T {
@@ -22,8 +22,8 @@ import {
CalendarProviderListCalendarsParams,
CalendarProviderListEventsParams,
CalendarProviderListEventsResult,
CalendarProviderName,
} from './def';
import { CalendarProviderName } from './factory';
import { CalendarSyncTokenInvalid } from './google';
const XML_PARSER = new XMLParser({
@@ -113,7 +113,7 @@ const isRedirectStatus = (status: number) =>
const splitHeaderTokens = (value: string) =>
value
.split(/,(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)/)
.split(/,(?=(?:[^"]*"[^"]*")*[^"]*$)/)
.map(token => token.trim())
.filter(Boolean);
@@ -2,12 +2,7 @@ import { Inject, Injectable, Logger } from '@nestjs/common';
import type { CalendarAccount } from '@prisma/client';
import { CalendarProviderRequestError, Config, OnEvent } from '../../../base';
import { CalendarProviderFactory } from './factory';
export enum CalendarProviderName {
Google = 'google',
CalDAV = 'caldav',
}
import { CalendarProviderFactory, CalendarProviderName } from './factory';
export interface CalendarProviderTokens {
accessToken: string;
@@ -1,12 +1,20 @@
import { Injectable, Logger } from '@nestjs/common';
import type { CalendarProvider } from './def';
import { CalendarProviderName } from './def';
export enum CalendarProviderName {
Google = 'google',
CalDAV = 'caldav',
}
export interface CalendarProviderRef {
provider: CalendarProviderName;
}
@Injectable()
export class CalendarProviderFactory {
export class CalendarProviderFactory<
TProvider extends CalendarProviderRef = CalendarProviderRef,
> {
private readonly logger = new Logger(CalendarProviderFactory.name);
readonly #providers = new Map<CalendarProviderName, CalendarProvider>();
readonly #providers = new Map<CalendarProviderName, TProvider>();
get providers() {
return Array.from(this.#providers.keys());
@@ -16,12 +24,12 @@ export class CalendarProviderFactory {
return this.#providers.get(name);
}
register(provider: CalendarProvider) {
register(provider: TProvider) {
this.#providers.set(provider.provider, provider);
this.logger.log(`Calendar provider [${provider.provider}] registered.`);
}
unregister(provider: CalendarProvider) {
unregister(provider: TProvider) {
this.#providers.delete(provider.provider);
this.logger.log(`Calendar provider [${provider.provider}] unregistered.`);
}
@@ -1,17 +1,17 @@
import { Injectable } from '@nestjs/common';
import { CalendarProviderRequestError } from '../../../base';
import { CalendarProvider } from './def';
import {
CalendarProvider,
CalendarProviderEvent,
CalendarProviderListCalendarsParams,
CalendarProviderListEventsParams,
CalendarProviderListEventsResult,
CalendarProviderName,
CalendarProviderTokens,
CalendarProviderWatchParams,
CalendarProviderWatchResult,
} from './def';
import { CalendarProviderName } from './factory';
export class CalendarSyncTokenInvalid extends Error {
readonly code = 'calendar_sync_token_invalid';
@@ -14,9 +14,8 @@ export type {
CalendarProviderWatchParams,
CalendarProviderWatchResult,
} from './def';
export { CalendarProviderName } from './def';
export { CalendarProvider } from './def';
export { CalendarProviderFactory } from './factory';
export { CalendarProviderFactory, CalendarProviderName } from './factory';
export { CalendarSyncTokenInvalid, GoogleCalendarProvider } from './google';
export const CalendarProviders = [GoogleCalendarProvider, CalDAVProvider];
@@ -18,10 +18,10 @@ import {
CalendarProvider,
CalendarProviderEvent,
CalendarProviderEventTime,
CalendarProviderFactory,
CalendarProviderName,
CalendarSyncTokenInvalid,
} from './providers';
import { CalendarProviderFactory } from './providers';
import type { LinkCalDAVAccountInput } from './types';
const TOKEN_REFRESH_SKEW_MS = 60 * 1000;
@@ -35,7 +35,7 @@ export class CalendarService {
constructor(
private readonly models: Models,
private readonly providerFactory: CalendarProviderFactory,
private readonly providerFactory: CalendarProviderFactory<CalendarProvider>,
private readonly mutex: Mutex,
private readonly config: Config,
private readonly url: URLHelper
@@ -105,11 +105,11 @@ export class CalendarService {
const accessToken = accountTokens.accessToken;
if (accessToken) {
await Promise.allSettled(
needToStopChannel.map(s => {
needToStopChannel.map(async s => {
if (!s.customChannelId || !s.customResourceId) {
return Promise.resolve();
return;
}
return provider.stopChannel?.({
return await provider.stopChannel?.({
accessToken,
channelId: s.customChannelId,
resourceId: s.customResourceId,
@@ -654,8 +654,11 @@ export class CalendarService {
}
const zone = time.timeZone ?? fallbackTimezone ?? 'UTC';
if (!time.date) {
throw new Error('Calendar provider returned all-day event without date');
}
return {
date: this.convertDateToUtc(time.date!, zone),
date: this.convertDateToUtc(time.date, zone),
allDay: true,
};
}
@@ -49,7 +49,7 @@ import {
FileChunkSimilarity,
Models,
} from '../../../models';
import { CopilotEmbeddingJob } from '../embedding';
import { CopilotEmbeddingJob } from '../embedding/job';
import { COPILOT_LOCKER, CopilotType } from '../resolver';
import { ChatSessionService } from '../session';
import { CopilotStorage } from '../storage';
@@ -15,7 +15,8 @@ import {
ContextFile,
Models,
} from '../../../models';
import { type EmbeddingClient, getEmbeddingClient } from '../embedding';
import { getEmbeddingClient } from '../embedding/client';
import type { EmbeddingClient } from '../embedding/types';
import { ContextSession } from './session';
const CONTEXT_SESSION_KEY = 'context-session';
@@ -11,7 +11,7 @@ import {
FileChunkSimilarity,
Models,
} from '../../../models';
import { EmbeddingClient } from '../embedding';
import { EmbeddingClient } from '../embedding/types';
export class ContextSession implements AsyncDisposable {
constructor(
@@ -47,14 +47,14 @@ import {
} from '../../base';
import { ServerFeature, ServerService } from '../../core';
import { CurrentUser, Public } from '../../core/auth';
import { CopilotContextService } from './context';
import { CopilotContextService } from './context/service';
import { CopilotProviderFactory } from './providers/factory';
import type { CopilotProvider } from './providers/provider';
import {
CopilotProvider,
CopilotProviderFactory,
ModelInputType,
ModelOutputType,
StreamObject,
} from './providers';
type StreamObject,
} from './providers/types';
import { StreamObjectParser } from './providers/utils';
import { ChatSession, ChatSessionService } from './session';
import { CopilotStorage } from './storage';
@@ -12,14 +12,14 @@ import {
Embedding,
EMBEDDING_DIMENSIONS,
} from '../../../models';
import { PromptService } from '../prompt';
import { PromptService } from '../prompt/service';
import { CopilotProviderFactory } from '../providers/factory';
import type { CopilotProvider } from '../providers/provider';
import {
type CopilotProvider,
CopilotProviderFactory,
type ModelFullConditions,
ModelInputType,
ModelOutputType,
} from '../providers';
} from '../providers/types';
import { EmbeddingClient, type ReRankResult } from './types';
const EMBEDDING_MODEL = 'gemini-embedding-001';
@@ -8,7 +8,7 @@ import { DocReader, DocWriter } from '../../../core/doc';
import { AccessController } from '../../../core/permission';
import { clearEmbeddingChunk } from '../../../models';
import { IndexerService } from '../../indexer';
import { CopilotContextService } from '../context';
import { CopilotContextService } from '../context/service';
@Injectable()
export class WorkspaceMcpProvider {
@@ -4,7 +4,11 @@ import { AiPrompt } from '@prisma/client';
import Mustache from 'mustache';
import { getTokenEncoder } from '../../../native';
import { PromptConfig, PromptMessage, PromptParams } from '../providers';
import type {
PromptConfig,
PromptMessage,
PromptParams,
} from '../providers/types';
// disable escaping
Mustache.escape = (text: string) => text;
@@ -1,7 +1,7 @@
import { Logger } from '@nestjs/common';
import { AiPrompt, PrismaClient } from '@prisma/client';
import { PromptConfig, PromptMessage } from '../providers';
import type { PromptConfig, PromptMessage } from '../providers/types';
type Prompt = Omit<
AiPrompt,
@@ -8,7 +8,7 @@ import {
PromptConfigSchema,
PromptMessage,
PromptMessageSchema,
} from '../providers';
} from '../providers/types';
import { ChatPrompt } from './chat-prompt';
import {
CopilotPromptScenario,
@@ -13,8 +13,8 @@ import { DocReader, DocWriter } from '../../../core/doc';
import { AccessController } from '../../../core/permission';
import { Models } from '../../../models';
import { IndexerService } from '../../indexer';
import { CopilotContextService } from '../context';
import { PromptService } from '../prompt';
import { CopilotContextService } from '../context/service';
import { PromptService } from '../prompt/service';
import {
buildBlobContentGetter,
buildContentGetter,
@@ -42,9 +42,9 @@ import { AccessController, DocAction } from '../../core/permission';
import { UserType } from '../../core/user';
import type { ListSessionOptions, UpdateChatSession } from '../../models';
import { CopilotCronJobs } from './cron';
import { PromptService } from './prompt';
import { PromptMessage, StreamObject } from './providers';
import { PromptService } from './prompt/service';
import { CopilotProviderFactory } from './providers/factory';
import type { PromptMessage, StreamObject } from './providers/types';
import { ChatSessionService } from './session';
import { CopilotStorage } from './storage';
import { type ChatHistory, type ChatMessage, SubmittedMessage } from './types';
@@ -28,13 +28,14 @@ import {
import { SubscriptionService } from '../payment/service';
import { SubscriptionPlan, SubscriptionStatus } from '../payment/types';
import { ChatMessageCache } from './message';
import { ChatPrompt, PromptService } from './prompt';
import { ChatPrompt } from './prompt/chat-prompt';
import { PromptService } from './prompt/service';
import { CopilotProviderFactory } from './providers/factory';
import {
CopilotProviderFactory,
ModelOutputType,
PromptMessage,
PromptParams,
} from './providers';
type PromptMessage,
type PromptParams,
} from './providers/types';
import {
type ChatHistory,
type ChatMessage,
@@ -322,7 +323,7 @@ export class ChatSessionService {
private stripNullBytes(value?: string | null): string {
if (!value) return '';
return value.replace(/\u0000/g, '');
return value.replaceAll('\0', '');
}
private isNullByteError(error: unknown): boolean {
@@ -3,9 +3,8 @@ import { tool } from 'ai';
import { z } from 'zod';
import { AccessController } from '../../../core/permission';
import type { ContextSession } from '../context/session';
import type { CopilotChatOptions } from '../providers';
import { toolError } from './error';
import type { ContextSession, CopilotChatOptions } from './types';
const logger = new Logger('ContextBlobReadTool');
@@ -2,9 +2,9 @@ import { Logger } from '@nestjs/common';
import { tool } from 'ai';
import { z } from 'zod';
import type { PromptService } from '../prompt';
import type { CopilotProviderFactory } from '../providers';
import { toolError } from './error';
import type { CopilotProviderFactory, PromptService } from './types';
const logger = new Logger('CodeArtifactTool');
/**
* A copilot tool that produces a completely self-contained HTML artifact.
@@ -2,9 +2,8 @@ import { Logger } from '@nestjs/common';
import { tool } from 'ai';
import { z } from 'zod';
import type { PromptService } from '../prompt';
import type { CopilotProviderFactory } from '../providers';
import { toolError } from './error';
import type { CopilotProviderFactory, PromptService } from './types';
const logger = new Logger('ConversationSummaryTool');
@@ -2,9 +2,8 @@ import { Logger } from '@nestjs/common';
import { tool } from 'ai';
import { z } from 'zod';
import type { PromptService } from '../prompt';
import type { CopilotProviderFactory } from '../providers';
import { toolError } from './error';
import type { CopilotProviderFactory, PromptService } from './types';
const logger = new Logger('DocComposeTool');
@@ -3,8 +3,11 @@ import { z } from 'zod';
import { DocReader } from '../../../core/doc';
import { AccessController } from '../../../core/permission';
import { type PromptService } from '../prompt';
import type { CopilotChatOptions, CopilotProviderFactory } from '../providers';
import type {
CopilotChatOptions,
CopilotProviderFactory,
PromptService,
} from './types';
const CodeEditSchema = z
.array(
@@ -3,8 +3,8 @@ import { z } from 'zod';
import type { AccessController } from '../../../core/permission';
import type { IndexerService, SearchDoc } from '../../indexer';
import type { CopilotChatOptions } from '../providers';
import { toolError } from './error';
import type { CopilotChatOptions } from './types';
export const buildDocKeywordSearchGetter = (
ac: AccessController,
@@ -5,8 +5,8 @@ import { z } from 'zod';
import { DocReader } from '../../../core/doc';
import { AccessController } from '../../../core/permission';
import { Models, publicUserSelect } from '../../../models';
import type { CopilotChatOptions } from '../providers';
import { toolError } from './error';
import type { CopilotChatOptions } from './types';
const logger = new Logger('DocReadTool');
@@ -8,10 +8,12 @@ import {
clearEmbeddingChunk,
type Models,
} from '../../../models';
import type { CopilotContextService } from '../context';
import type { ContextSession } from '../context/session';
import type { CopilotChatOptions } from '../providers';
import { toolError } from './error';
import type {
ContextSession,
CopilotChatOptions,
CopilotContextService,
} from './types';
export const buildDocSearchGetter = (
ac: AccessController,
@@ -4,8 +4,8 @@ import { z } from 'zod';
import { DocWriter } from '../../../core/doc';
import { AccessController } from '../../../core/permission';
import type { CopilotChatOptions } from '../providers';
import { toolError } from './error';
import type { CopilotChatOptions } from './types';
const logger = new Logger('DocWriteTool');
@@ -2,9 +2,8 @@ import { Logger } from '@nestjs/common';
import { tool } from 'ai';
import { z } from 'zod';
import type { PromptService } from '../prompt';
import type { CopilotProviderFactory } from '../providers';
import { toolError } from './error';
import type { CopilotProviderFactory, PromptService } from './types';
const logger = new Logger('SectionEditTool');
@@ -0,0 +1,5 @@
export type { CopilotContextService } from '../context/service';
export type { ContextSession } from '../context/session';
export type { PromptService } from '../prompt/service';
export type { CopilotProviderFactory } from '../providers/factory';
export type { CopilotChatOptions } from '../providers/types';
@@ -125,6 +125,7 @@ export class CopilotTranscriptionResolver {
user.id,
workspaceId,
blobId,
// eslint-disable-next-line @typescript-eslint/await-thenable
await Promise.all(allBlobs)
);
@@ -15,14 +15,10 @@ import {
sniffMime,
} from '../../../base';
import { Models } from '../../../models';
import { PromptService } from '../prompt';
import {
CopilotProvider,
CopilotProviderFactory,
CopilotProviderType,
ModelOutputType,
PromptMessage,
} from '../providers';
import { PromptService } from '../prompt/service';
import type { CopilotProvider, PromptMessage } from '../providers';
import { CopilotProviderFactory } from '../providers/factory';
import { CopilotProviderType, ModelOutputType } from '../providers/types';
import { CopilotStorage } from '../storage';
import {
AudioBlobInfos,
@@ -171,7 +167,7 @@ export class CopilotTranscriptionService {
if (payload.success) {
let { url, mimeType, infos } = payload.data;
infos = infos || [];
if (url && mimeType && !infos.find(i => i.url === url)) {
if (url && mimeType && !infos.some(i => i.url === url)) {
infos.push({ url, mimeType });
}
@@ -1,7 +1,7 @@
import { z } from 'zod';
import type { ChatPrompt } from './prompt';
import { PromptMessageSchema, PureMessageSchema } from './providers';
import type { ChatPrompt } from './prompt/chat-prompt';
import { PromptMessageSchema, PureMessageSchema } from './providers/types';
const takeFirst = (v: unknown) => (Array.isArray(v) ? v[0] : v);
@@ -3,7 +3,7 @@ import { Readable } from 'node:stream';
import type { Request } from 'express';
import { OneMB, readBufferWithLimit } from '../../base';
import type { PromptTools } from './providers';
import type { PromptTools } from './providers/types';
import type { ToolsConfig } from './types';
export const MAX_EMBEDDABLE_SIZE = 50 * OneMB;
@@ -4,7 +4,7 @@ import { fileURLToPath } from 'node:url';
import { Logger } from '@nestjs/common';
import Piscina from 'piscina';
import { CopilotChatOptions } from '../providers';
import type { CopilotChatOptions } from '../providers/types';
import type { NodeExecuteResult, NodeExecutor } from './executor';
import { getWorkflowExecutor, NodeExecuteState } from './executor';
import type {
@@ -1,6 +1,6 @@
import { Injectable, Logger } from '@nestjs/common';
import { CopilotChatOptions } from '../providers';
import type { CopilotChatOptions } from '../providers/types';
import { WorkflowGraphList } from './graph';
import { WorkflowNode } from './node';
import type { WorkflowGraph, WorkflowGraphInstances } from './types';
@@ -1,6 +1,6 @@
import { Logger } from '@nestjs/common';
import { CopilotChatOptions } from '../providers';
import type { CopilotChatOptions } from '../providers/types';
import { NodeExecuteState } from './executor';
import { WorkflowNode } from './node';
import type { WorkflowGraphInstances, WorkflowNodeState } from './types';
@@ -132,10 +132,11 @@ export class AppleOAuthProvider extends OAuthProvider {
{ method: 'GET' },
{ treatServerErrorAsInvalid: true }
);
const idToken = tokens.idToken;
const payload = await new Promise<JwtPayload>((resolve, reject) => {
jwt.verify(
tokens.idToken!,
idToken,
(header, callback) => {
const key = keys.find(key => key.kid === header.kid);
if (!key) {
@@ -29,6 +29,36 @@ const SHOULD_MANUAL_REDIRECT =
BUILD_CONFIG.isAndroid || BUILD_CONFIG.isIOS || BUILD_CONFIG.isElectron;
const UPLOAD_REQUEST_TIMEOUT = 0;
function toStrictArrayBuffer(
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;
}
export class CloudBlobStorage extends BlobStorageBase {
static readonly identifier = 'CloudBlobStorage';
override readonly isReadonly = false;
@@ -127,8 +157,11 @@ export class CloudBlobStorage extends BlobStorageBase {
if (upload.method === BlobUploadMethod.PRESIGNED) {
try {
if (!upload.uploadUrl) {
throw new Error('Missing upload URL for presigned upload.');
}
await this.uploadViaPresigned(
upload.uploadUrl!,
upload.uploadUrl,
upload.headers,
blob.data,
signal
@@ -143,15 +176,20 @@ export class CloudBlobStorage extends BlobStorageBase {
if (upload.method === BlobUploadMethod.MULTIPART) {
try {
if (!upload.uploadId || !upload.partSize) {
throw new Error(
'Missing upload ID or part size for multipart upload.'
);
}
const parts = await this.uploadViaMultipart(
blob.key,
upload.uploadId!,
upload.partSize!,
upload.uploadId,
upload.partSize,
blob.data,
upload.uploadedParts,
signal
);
await this.completeUpload(blob.key, upload.uploadId!, parts, signal);
await this.completeUpload(blob.key, upload.uploadId, parts, signal);
return;
} catch {
if (upload.uploadId) {
@@ -216,7 +254,9 @@ export class CloudBlobStorage extends BlobStorageBase {
query: setBlobMutation,
variables: {
workspaceId: this.options.id,
blob: new File([blob.data], blob.key, { type: blob.mime }),
blob: new File([toStrictArrayBuffer(blob.data)], blob.key, {
type: blob.mime,
}),
},
context: { signal },
timeout: UPLOAD_REQUEST_TIMEOUT,
@@ -232,7 +272,7 @@ export class CloudBlobStorage extends BlobStorageBase {
const res = await this.fetchWithTimeout(uploadUrl, {
method: 'PUT',
headers: headers ?? undefined,
body: data,
body: toStrictArrayBuffer(data),
signal,
timeout: UPLOAD_REQUEST_TIMEOUT,
});
@@ -275,7 +315,7 @@ export class CloudBlobStorage extends BlobStorageBase {
{
method: 'PUT',
headers: part.workspace.blobUploadPartUrl.headers ?? undefined,
body: chunk,
body: toStrictArrayBuffer(chunk),
signal,
timeout: UPLOAD_REQUEST_TIMEOUT,
}
@@ -141,10 +141,10 @@ export class CloudIndexerStorage extends IndexerStorageBase {
}
override async refreshIfNeed(): Promise<void> {
return Promise.resolve();
return;
}
override async indexVersion(): Promise<number> {
return Promise.resolve(1);
return 1;
}
}
@@ -222,6 +222,6 @@ export class IndexedDBIndexerStorage extends IndexerStorageBase {
// Get the current indexer version
// increase this number to re-index all docs
async indexVersion(): Promise<number> {
return Promise.resolve(1);
return 1;
}
}
@@ -1,4 +1,5 @@
import { merge, Observable, of, Subject } from 'rxjs';
import type { Observable } from 'rxjs';
import { merge, of, Subject } from 'rxjs';
import { filter, throttleTime } from 'rxjs/operators';
import { share } from '../../../connection';
@@ -194,9 +195,9 @@ export class SqliteIndexerStorage extends IndexerStorageBase {
const schema = IndexerSchema[table];
for (const [field, values] of document.fields) {
const fieldSchema = schema[field];
// @ts-expect-error
// @ts-expect-error -- IndexerSchema uses runtime-keyed fields from each table schema.
const shouldIndex = fieldSchema.index !== false;
// @ts-expect-error
// @ts-expect-error -- IndexerSchema uses runtime-keyed fields from each table schema.
const shouldStore = fieldSchema.store !== false;
if (!shouldStore && !shouldIndex) continue;
@@ -86,9 +86,9 @@ export class DummyIndexerStorage extends IndexerStorageBase {
return Promise.resolve();
}
override async refreshIfNeed(): Promise<void> {
return Promise.resolve();
return;
}
override async indexVersion(): Promise<number> {
return Promise.resolve(0);
return 0;
}
}
@@ -190,6 +190,7 @@ export class BlobSyncImpl implements BlobSync {
): Promise<void> {
return Promise.race([
Promise.all(
// eslint-disable-next-line @typescript-eslint/await-thenable
peerId
? [this.fullDownloadPeer(peerId)]
: this.peers.map(p => this.fullDownloadPeer(p.peerId))
@@ -125,8 +125,8 @@ export class TelemetryManager {
private mergeContext(event: TelemetryEvent): TelemetryEvent {
const mergedUserProps = {
...(this.context.userProperties ?? {}),
...(event.userProperties ?? {}),
...this.context.userProperties,
...event.userProperties,
};
const mergedContext = {
+7 -7
View File
@@ -1,6 +1,6 @@
import { Buffer } from 'node:buffer';
import type { Buffer } from 'node:buffer';
import { stringify as stringifyQuery } from 'node:querystring';
import { Readable } from 'node:stream';
import type { Readable } from 'node:stream';
import aws4 from 'aws4';
import { XMLParser } from 'fast-xml-parser';
@@ -180,16 +180,16 @@ export function parseListPartsXml(xml: string): ParsedListParts {
function buildEndpoint(config: S3CompatConfig) {
const url = new URL(config.endpoint);
if (config.forcePathStyle) {
const segments = url.pathname.split('/').filter(Boolean);
if (segments[0] !== config.bucket) {
const firstSegment = url.pathname.split('/').find(Boolean);
if (firstSegment !== config.bucket) {
url.pathname = joinPath(url.pathname, config.bucket);
}
return url;
}
const pathSegments = url.pathname.split('/').filter(Boolean);
const firstSegment = url.pathname.split('/').find(Boolean);
const hostHasBucket = url.hostname.startsWith(`${config.bucket}.`);
const pathHasBucket = pathSegments[0] === config.bucket;
const pathHasBucket = firstSegment === config.bucket;
if (!hostHasBucket && !pathHasBucket) {
url.hostname = `${config.bucket}.${url.hostname}`;
}
@@ -297,7 +297,7 @@ export class S3Compat implements S3CompatClient {
const expiresInSeconds = this.presignConfig.expiresInSeconds;
const path = this.buildObjectPath(key);
const queryString = buildQuery({
...(query ?? {}),
...query,
'X-Amz-Expires': expiresInSeconds,
});
const requestPath = queryString ? `${path}?${queryString}` : path;
@@ -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 () => {
@@ -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

Some files were not shown because too many files have changed in this diff Show More