mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-03-25 00:30:08 +08:00
Compare commits
5 Commits
b3baf6ccd4
...
fix/issue-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b586fc0dea | ||
|
|
4b57d9581a | ||
|
|
b177024818 | ||
|
|
8ae9994443 | ||
|
|
505505a1e7 |
4
.github/renovate.json
vendored
4
.github/renovate.json
vendored
@@ -63,7 +63,7 @@
|
||||
"groupName": "opentelemetry",
|
||||
"matchPackageNames": [
|
||||
"/^@opentelemetry/",
|
||||
"/^@google-cloud/opentelemetry-/"
|
||||
"/^@google-cloud\/opentelemetry-/"
|
||||
]
|
||||
}
|
||||
],
|
||||
@@ -79,7 +79,7 @@
|
||||
"customManagers": [
|
||||
{
|
||||
"customType": "regex",
|
||||
"managerFilePatterns": ["/^rust-toolchain\\.toml?$/"],
|
||||
"fileMatch": ["^rust-toolchain\\.toml?$"],
|
||||
"matchStrings": [
|
||||
"channel\\s*=\\s*\"(?<currentValue>\\d+\\.\\d+(\\.\\d+)?)\""
|
||||
],
|
||||
|
||||
@@ -3,11 +3,8 @@ import {
|
||||
EdgelessCRUDIdentifier,
|
||||
TextUtils,
|
||||
} from '@blocksuite/affine-block-surface';
|
||||
import {
|
||||
MindmapElementModel,
|
||||
ShapeElementModel,
|
||||
TextResizing,
|
||||
} from '@blocksuite/affine-model';
|
||||
import type { ShapeElementModel } from '@blocksuite/affine-model';
|
||||
import { MindmapElementModel, TextResizing } from '@blocksuite/affine-model';
|
||||
import type { RichText } from '@blocksuite/affine-rich-text';
|
||||
import { ThemeProvider } from '@blocksuite/affine-shared/services';
|
||||
import { getSelectedRect } from '@blocksuite/affine-shared/utils';
|
||||
@@ -29,7 +26,7 @@ import { styleMap } from 'lit/directives/style-map.js';
|
||||
import * as Y from 'yjs';
|
||||
|
||||
export function mountShapeTextEditor(
|
||||
shapeElement: ShapeElementModel,
|
||||
shapeElement: { id: string; text?: Y.Text } | null | undefined,
|
||||
edgeless: BlockComponent
|
||||
) {
|
||||
const mountElm = edgeless.querySelector('.edgeless-mount-point');
|
||||
@@ -43,24 +40,27 @@ export function mountShapeTextEditor(
|
||||
const gfx = edgeless.std.get(GfxControllerIdentifier);
|
||||
const crud = edgeless.std.get(EdgelessCRUDIdentifier);
|
||||
|
||||
if (!shapeElement?.id) {
|
||||
console.error('Cannot mount text editor on an invalid shape element');
|
||||
return;
|
||||
}
|
||||
|
||||
const updatedElement = crud.getElementById(shapeElement.id);
|
||||
|
||||
if (!(updatedElement instanceof ShapeElementModel)) {
|
||||
if (!updatedElement || !('id' in updatedElement)) {
|
||||
console.error('Cannot mount text editor on a non-shape element');
|
||||
return;
|
||||
}
|
||||
|
||||
gfx.tool.setTool(DefaultTool);
|
||||
gfx.selection.set({
|
||||
elements: [shapeElement.id],
|
||||
elements: [updatedElement.id],
|
||||
editing: true,
|
||||
});
|
||||
|
||||
if (!shapeElement.text) {
|
||||
if (!updatedElement.text) {
|
||||
const text = new Y.Text();
|
||||
edgeless.std
|
||||
.get(EdgelessCRUDIdentifier)
|
||||
.updateElement(shapeElement.id, { text });
|
||||
crud.updateElement(updatedElement.id, { text });
|
||||
}
|
||||
|
||||
const shapeEditor = new EdgelessShapeTextEditor();
|
||||
@@ -280,6 +280,21 @@ export class EdgelessShapeTextEditor extends WithDisposable(ShadowlessElement) {
|
||||
this._unmount();
|
||||
}
|
||||
);
|
||||
|
||||
this.disposables.addFromEvent(
|
||||
this.inlineEditorContainer,
|
||||
'compositionupdate',
|
||||
() => {
|
||||
this._updateElementWH();
|
||||
}
|
||||
);
|
||||
this.disposables.addFromEvent(
|
||||
this.inlineEditorContainer,
|
||||
'compositionend',
|
||||
() => {
|
||||
this._updateElementWH();
|
||||
}
|
||||
);
|
||||
})
|
||||
.catch(console.error);
|
||||
|
||||
|
||||
@@ -17,14 +17,7 @@ export async function printToPdf(
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
const iframe = document.createElement('iframe');
|
||||
document.body.append(iframe);
|
||||
// Use a hidden but rendering-enabled state instead of display: none
|
||||
Object.assign(iframe.style, {
|
||||
visibility: 'hidden',
|
||||
position: 'absolute',
|
||||
width: '0',
|
||||
height: '0',
|
||||
border: 'none',
|
||||
});
|
||||
iframe.style.display = 'none';
|
||||
iframe.srcdoc = '<!DOCTYPE html>';
|
||||
iframe.onload = async () => {
|
||||
if (!iframe.contentWindow) {
|
||||
@@ -35,44 +28,6 @@ export async function printToPdf(
|
||||
reject(new Error('Root element not defined, unable to print pdf'));
|
||||
return;
|
||||
}
|
||||
|
||||
const doc = iframe.contentWindow.document;
|
||||
|
||||
doc.write(`<!DOCTYPE html><html><head><style>@media print {
|
||||
html, body {
|
||||
height: initial !important;
|
||||
overflow: initial !important;
|
||||
print-color-adjust: exact;
|
||||
-webkit-print-color-adjust: exact;
|
||||
color: #000 !important;
|
||||
background: #fff !important;
|
||||
color-scheme: light !important;
|
||||
}
|
||||
::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
:root, body {
|
||||
--affine-text-primary: #000 !important;
|
||||
--affine-text-secondary: #111 !important;
|
||||
--affine-text-tertiary: #333 !important;
|
||||
--affine-background-primary: #fff !important;
|
||||
--affine-background-secondary: #fff !important;
|
||||
--affine-background-tertiary: #fff !important;
|
||||
}
|
||||
body, [data-theme='dark'] {
|
||||
color: #000 !important;
|
||||
background: #fff !important;
|
||||
}
|
||||
body * {
|
||||
color: #000 !important;
|
||||
-webkit-text-fill-color: #000 !important;
|
||||
}
|
||||
:root {
|
||||
--affine-note-shadow-box: none !important;
|
||||
--affine-note-shadow-sticker: none !important;
|
||||
}
|
||||
}</style></head><body></body></html>`);
|
||||
doc.close();
|
||||
iframe.contentWindow.document
|
||||
.write(`<!DOCTYPE html><html><head><style>@media print {
|
||||
html, body {
|
||||
@@ -116,7 +71,7 @@ export async function printToPdf(
|
||||
for (const element of document.styleSheets) {
|
||||
try {
|
||||
for (const cssRule of element.cssRules) {
|
||||
const target = doc.styleSheets[0];
|
||||
const target = iframe.contentWindow.document.styleSheets[0];
|
||||
target.insertRule(cssRule.cssText, target.cssRules.length);
|
||||
}
|
||||
} catch (e) {
|
||||
@@ -131,33 +86,12 @@ export async function printToPdf(
|
||||
}
|
||||
}
|
||||
|
||||
// Recursive function to find all canvases, including those in shadow roots
|
||||
const findAllCanvases = (root: Node): HTMLCanvasElement[] => {
|
||||
const canvases: HTMLCanvasElement[] = [];
|
||||
const traverse = (node: Node) => {
|
||||
if (node instanceof HTMLCanvasElement) {
|
||||
canvases.push(node);
|
||||
}
|
||||
if (node instanceof HTMLElement || node instanceof ShadowRoot) {
|
||||
node.childNodes.forEach(traverse);
|
||||
}
|
||||
if (node instanceof HTMLElement && node.shadowRoot) {
|
||||
traverse(node.shadowRoot);
|
||||
}
|
||||
};
|
||||
traverse(root);
|
||||
return canvases;
|
||||
};
|
||||
|
||||
// convert all canvas to image
|
||||
const canvasImgObjectUrlMap = new Map<string, string>();
|
||||
const allCanvas = findAllCanvases(rootElement);
|
||||
const allCanvas = rootElement.getElementsByTagName('canvas');
|
||||
let canvasKey = 1;
|
||||
const canvasToKeyMap = new Map<HTMLCanvasElement, string>();
|
||||
|
||||
for (const canvas of allCanvas) {
|
||||
const key = canvasKey.toString();
|
||||
canvasToKeyMap.set(canvas, key);
|
||||
canvas.dataset['printToPdfCanvasKey'] = canvasKey.toString();
|
||||
canvasKey++;
|
||||
const canvasImgObjectUrl = await new Promise<Blob | null>(resolve => {
|
||||
try {
|
||||
@@ -172,42 +106,20 @@ export async function printToPdf(
|
||||
);
|
||||
continue;
|
||||
}
|
||||
canvasImgObjectUrlMap.set(key, URL.createObjectURL(canvasImgObjectUrl));
|
||||
canvasImgObjectUrlMap.set(
|
||||
canvas.dataset['printToPdfCanvasKey'],
|
||||
URL.createObjectURL(canvasImgObjectUrl)
|
||||
);
|
||||
}
|
||||
|
||||
// Recursive deep clone that flattens Shadow DOM into Light DOM
|
||||
const deepCloneWithShadows = (node: Node): Node => {
|
||||
const clone = doc.importNode(node, false);
|
||||
|
||||
if (
|
||||
clone instanceof HTMLCanvasElement &&
|
||||
node instanceof HTMLCanvasElement
|
||||
) {
|
||||
const key = canvasToKeyMap.get(node);
|
||||
if (key) {
|
||||
clone.dataset['printToPdfCanvasKey'] = key;
|
||||
}
|
||||
}
|
||||
|
||||
const appendChildren = (source: Node) => {
|
||||
source.childNodes.forEach(child => {
|
||||
(clone as Element).append(deepCloneWithShadows(child));
|
||||
});
|
||||
};
|
||||
|
||||
if (node instanceof HTMLElement && node.shadowRoot) {
|
||||
appendChildren(node.shadowRoot);
|
||||
}
|
||||
appendChildren(node);
|
||||
|
||||
return clone;
|
||||
};
|
||||
|
||||
const importedRoot = deepCloneWithShadows(rootElement) as HTMLDivElement;
|
||||
const importedRoot = iframe.contentWindow.document.importNode(
|
||||
rootElement,
|
||||
true
|
||||
) as HTMLDivElement;
|
||||
|
||||
// force light theme in print iframe
|
||||
doc.documentElement.dataset.theme = 'light';
|
||||
doc.body.dataset.theme = 'light';
|
||||
iframe.contentWindow.document.documentElement.dataset.theme = 'light';
|
||||
iframe.contentWindow.document.body.dataset.theme = 'light';
|
||||
importedRoot.dataset.theme = 'light';
|
||||
|
||||
// draw saved canvas image to canvas
|
||||
@@ -226,67 +138,17 @@ export async function printToPdf(
|
||||
}
|
||||
}
|
||||
|
||||
// Remove lazy loading from all images and force reload
|
||||
const allImages = importedRoot.querySelectorAll('img');
|
||||
allImages.forEach(img => {
|
||||
img.removeAttribute('loading');
|
||||
const src = img.getAttribute('src');
|
||||
if (src) img.setAttribute('src', src);
|
||||
});
|
||||
|
||||
// append to iframe
|
||||
doc.body.append(importedRoot);
|
||||
// append to iframe and print
|
||||
iframe.contentWindow.document.body.append(importedRoot);
|
||||
|
||||
await options.beforeprint?.(iframe);
|
||||
|
||||
// Robust image waiting logic
|
||||
const waitForImages = async (container: HTMLElement) => {
|
||||
const images: HTMLImageElement[] = [];
|
||||
const view = container.ownerDocument.defaultView;
|
||||
if (!view) return;
|
||||
|
||||
const findImages = (root: Node) => {
|
||||
if (root instanceof view.HTMLImageElement) {
|
||||
images.push(root);
|
||||
}
|
||||
if (
|
||||
root instanceof view.HTMLElement ||
|
||||
root instanceof view.ShadowRoot
|
||||
) {
|
||||
root.childNodes.forEach(findImages);
|
||||
}
|
||||
if (root instanceof view.HTMLElement && root.shadowRoot) {
|
||||
findImages(root.shadowRoot);
|
||||
}
|
||||
};
|
||||
|
||||
findImages(container);
|
||||
|
||||
await Promise.all(
|
||||
images.map(img => {
|
||||
if (img.complete) {
|
||||
if (img.naturalWidth === 0) {
|
||||
console.warn('Image failed to load:', img.src);
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
return new Promise(resolve => {
|
||||
img.onload = resolve;
|
||||
img.onerror = resolve;
|
||||
});
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
await waitForImages(importedRoot);
|
||||
|
||||
// browser may take some time to load font or other resources
|
||||
await (doc.fonts?.ready ??
|
||||
new Promise<void>(resolve => {
|
||||
setTimeout(() => {
|
||||
resolve();
|
||||
}, 1000);
|
||||
}));
|
||||
// browser may take some time to load font
|
||||
await new Promise<void>(resolve => {
|
||||
setTimeout(() => {
|
||||
resolve();
|
||||
}, 1000);
|
||||
});
|
||||
|
||||
iframe.contentWindow.onafterprint = async () => {
|
||||
iframe.remove();
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import type { MindMapView } from '@blocksuite/affine/gfx/mindmap';
|
||||
import { mountShapeTextEditor } from '@blocksuite/affine/gfx/shape';
|
||||
import { LayoutType, type MindmapElementModel } from '@blocksuite/affine-model';
|
||||
import { Bound } from '@blocksuite/global/gfx';
|
||||
import type { GfxController } from '@blocksuite/std/gfx';
|
||||
import { beforeEach, describe, expect, test } from 'vitest';
|
||||
import { beforeEach, describe, expect, test, vi } from 'vitest';
|
||||
|
||||
import { click, pointermove, wait } from '../utils/common.js';
|
||||
import { getDocRootBlock } from '../utils/edgeless.js';
|
||||
@@ -36,6 +37,39 @@ describe('mindmap', () => {
|
||||
return cleanup;
|
||||
});
|
||||
|
||||
test('should update mindmap node editor size on compositionupdate', async () => {
|
||||
const mindmapId = gfx.surface!.addElement({
|
||||
type: 'mindmap',
|
||||
children: {
|
||||
text: 'root',
|
||||
},
|
||||
});
|
||||
const mindmap = () => gfx.getElementById(mindmapId) as MindmapElementModel;
|
||||
|
||||
const root = getDocRootBlock(window.doc, window.editor, 'edgeless');
|
||||
const rootNode = mindmap().tree.element;
|
||||
|
||||
mountShapeTextEditor(rootNode, root);
|
||||
await wait();
|
||||
|
||||
const shapeEditor = root.querySelector('edgeless-shape-text-editor') as
|
||||
| (HTMLElement & { inlineEditorContainer?: HTMLElement })
|
||||
| null;
|
||||
|
||||
expect(shapeEditor).not.toBeNull();
|
||||
|
||||
const updateSpy = vi.spyOn(shapeEditor as any, '_updateElementWH');
|
||||
|
||||
const compositionUpdate = new CompositionEvent('compositionupdate', {
|
||||
data: '拼',
|
||||
bubbles: true,
|
||||
});
|
||||
|
||||
shapeEditor!.inlineEditorContainer?.dispatchEvent(compositionUpdate);
|
||||
|
||||
expect(updateSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('delete the root node should remove all children', async () => {
|
||||
const tree = {
|
||||
text: 'root',
|
||||
|
||||
@@ -1,35 +1,12 @@
|
||||
import test from 'ava';
|
||||
import { z } from 'zod';
|
||||
|
||||
import type { DocReader } from '../../core/doc';
|
||||
import type { AccessController } from '../../core/permission';
|
||||
import type { Models } from '../../models';
|
||||
import { NativeLlmRequest, NativeLlmStreamEvent } from '../../native';
|
||||
import {
|
||||
ToolCallAccumulator,
|
||||
ToolCallLoop,
|
||||
ToolSchemaExtractor,
|
||||
} from '../../plugins/copilot/providers/loop';
|
||||
import {
|
||||
buildBlobContentGetter,
|
||||
createBlobReadTool,
|
||||
} from '../../plugins/copilot/tools/blob-read';
|
||||
import {
|
||||
buildDocKeywordSearchGetter,
|
||||
createDocKeywordSearchTool,
|
||||
} from '../../plugins/copilot/tools/doc-keyword-search';
|
||||
import {
|
||||
buildDocContentGetter,
|
||||
createDocReadTool,
|
||||
} from '../../plugins/copilot/tools/doc-read';
|
||||
import {
|
||||
buildDocSearchGetter,
|
||||
createDocSemanticSearchTool,
|
||||
} from '../../plugins/copilot/tools/doc-semantic-search';
|
||||
import {
|
||||
DOCUMENT_SYNC_PENDING_MESSAGE,
|
||||
LOCAL_WORKSPACE_SYNC_REQUIRED_MESSAGE,
|
||||
} from '../../plugins/copilot/tools/doc-sync';
|
||||
|
||||
test('ToolCallAccumulator should merge deltas and complete tool call', t => {
|
||||
const accumulator = new ToolCallAccumulator();
|
||||
@@ -309,210 +286,3 @@ test('ToolCallLoop should surface invalid JSON as tool error without executing',
|
||||
is_error: true,
|
||||
});
|
||||
});
|
||||
|
||||
test('doc_read should return specific sync errors for unavailable docs', async t => {
|
||||
const cases = [
|
||||
{
|
||||
name: 'local workspace without cloud sync',
|
||||
workspace: null,
|
||||
authors: null,
|
||||
markdown: null,
|
||||
expected: {
|
||||
type: 'error',
|
||||
name: 'Workspace Sync Required',
|
||||
message: LOCAL_WORKSPACE_SYNC_REQUIRED_MESSAGE,
|
||||
},
|
||||
docReaderCalled: false,
|
||||
},
|
||||
{
|
||||
name: 'cloud workspace document not synced to server yet',
|
||||
workspace: { id: 'ws-1' },
|
||||
authors: null,
|
||||
markdown: null,
|
||||
expected: {
|
||||
type: 'error',
|
||||
name: 'Document Sync Pending',
|
||||
message: DOCUMENT_SYNC_PENDING_MESSAGE('doc-1'),
|
||||
},
|
||||
docReaderCalled: false,
|
||||
},
|
||||
{
|
||||
name: 'cloud workspace document markdown not ready yet',
|
||||
workspace: { id: 'ws-1' },
|
||||
authors: {
|
||||
createdAt: new Date('2026-01-01T00:00:00.000Z'),
|
||||
updatedAt: new Date('2026-01-01T00:00:00.000Z'),
|
||||
createdByUser: null,
|
||||
updatedByUser: null,
|
||||
},
|
||||
markdown: null,
|
||||
expected: {
|
||||
type: 'error',
|
||||
name: 'Document Sync Pending',
|
||||
message: DOCUMENT_SYNC_PENDING_MESSAGE('doc-1'),
|
||||
},
|
||||
docReaderCalled: true,
|
||||
},
|
||||
] as const;
|
||||
|
||||
const ac = {
|
||||
user: () => ({
|
||||
workspace: () => ({ doc: () => ({ can: async () => true }) }),
|
||||
}),
|
||||
} as unknown as AccessController;
|
||||
|
||||
for (const testCase of cases) {
|
||||
let docReaderCalled = false;
|
||||
const docReader = {
|
||||
getDocMarkdown: async () => {
|
||||
docReaderCalled = true;
|
||||
return testCase.markdown;
|
||||
},
|
||||
} as unknown as DocReader;
|
||||
|
||||
const models = {
|
||||
workspace: {
|
||||
get: async () => testCase.workspace,
|
||||
},
|
||||
doc: {
|
||||
getAuthors: async () => testCase.authors,
|
||||
},
|
||||
} as unknown as Models;
|
||||
|
||||
const getDoc = buildDocContentGetter(ac, docReader, models);
|
||||
const tool = createDocReadTool(
|
||||
getDoc.bind(null, {
|
||||
user: 'user-1',
|
||||
workspace: 'workspace-1',
|
||||
})
|
||||
);
|
||||
|
||||
const result = await tool.execute?.({ doc_id: 'doc-1' }, {});
|
||||
|
||||
t.is(docReaderCalled, testCase.docReaderCalled, testCase.name);
|
||||
t.deepEqual(result, testCase.expected, testCase.name);
|
||||
}
|
||||
});
|
||||
|
||||
test('document search tools should return sync error for local workspace', async t => {
|
||||
const ac = {
|
||||
user: () => ({
|
||||
workspace: () => ({
|
||||
can: async () => true,
|
||||
docs: async () => [],
|
||||
}),
|
||||
}),
|
||||
} as unknown as AccessController;
|
||||
|
||||
const models = {
|
||||
workspace: {
|
||||
get: async () => null,
|
||||
},
|
||||
} as unknown as Models;
|
||||
|
||||
let keywordSearchCalled = false;
|
||||
const indexerService = {
|
||||
searchDocsByKeyword: async () => {
|
||||
keywordSearchCalled = true;
|
||||
return [];
|
||||
},
|
||||
} as unknown as Parameters<typeof buildDocKeywordSearchGetter>[1];
|
||||
|
||||
let semanticSearchCalled = false;
|
||||
const contextService = {
|
||||
matchWorkspaceAll: async () => {
|
||||
semanticSearchCalled = true;
|
||||
return [];
|
||||
},
|
||||
} as unknown as Parameters<typeof buildDocSearchGetter>[1];
|
||||
|
||||
const keywordTool = createDocKeywordSearchTool(
|
||||
buildDocKeywordSearchGetter(ac, indexerService, models).bind(null, {
|
||||
user: 'user-1',
|
||||
workspace: 'workspace-1',
|
||||
})
|
||||
);
|
||||
|
||||
const semanticTool = createDocSemanticSearchTool(
|
||||
buildDocSearchGetter(ac, contextService, null, models).bind(null, {
|
||||
user: 'user-1',
|
||||
workspace: 'workspace-1',
|
||||
})
|
||||
);
|
||||
|
||||
const keywordResult = await keywordTool.execute?.({ query: 'hello' }, {});
|
||||
const semanticResult = await semanticTool.execute?.({ query: 'hello' }, {});
|
||||
|
||||
t.false(keywordSearchCalled);
|
||||
t.false(semanticSearchCalled);
|
||||
t.deepEqual(keywordResult, {
|
||||
type: 'error',
|
||||
name: 'Workspace Sync Required',
|
||||
message: LOCAL_WORKSPACE_SYNC_REQUIRED_MESSAGE,
|
||||
});
|
||||
t.deepEqual(semanticResult, {
|
||||
type: 'error',
|
||||
name: 'Workspace Sync Required',
|
||||
message: LOCAL_WORKSPACE_SYNC_REQUIRED_MESSAGE,
|
||||
});
|
||||
});
|
||||
|
||||
test('doc_semantic_search should return empty array when nothing matches', async t => {
|
||||
const ac = {
|
||||
user: () => ({
|
||||
workspace: () => ({
|
||||
can: async () => true,
|
||||
docs: async () => [],
|
||||
}),
|
||||
}),
|
||||
} as unknown as AccessController;
|
||||
|
||||
const models = {
|
||||
workspace: {
|
||||
get: async () => ({ id: 'workspace-1' }),
|
||||
},
|
||||
} as unknown as Models;
|
||||
|
||||
const contextService = {
|
||||
matchWorkspaceAll: async () => [],
|
||||
} as unknown as Parameters<typeof buildDocSearchGetter>[1];
|
||||
|
||||
const semanticTool = createDocSemanticSearchTool(
|
||||
buildDocSearchGetter(ac, contextService, null, models).bind(null, {
|
||||
user: 'user-1',
|
||||
workspace: 'workspace-1',
|
||||
})
|
||||
);
|
||||
|
||||
const result = await semanticTool.execute?.({ query: 'hello' }, {});
|
||||
|
||||
t.deepEqual(result, []);
|
||||
});
|
||||
|
||||
test('blob_read should return explicit error when attachment context is missing', async t => {
|
||||
const ac = {
|
||||
user: () => ({
|
||||
workspace: () => ({
|
||||
allowLocal: () => ({
|
||||
can: async () => true,
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
} as unknown as AccessController;
|
||||
|
||||
const blobTool = createBlobReadTool(
|
||||
buildBlobContentGetter(ac, null).bind(null, {
|
||||
user: 'user-1',
|
||||
workspace: 'workspace-1',
|
||||
})
|
||||
);
|
||||
|
||||
const result = await blobTool.execute?.({ blob_id: 'blob-1' }, {});
|
||||
|
||||
t.deepEqual(result, {
|
||||
type: 'error',
|
||||
name: 'Blob Read Failed',
|
||||
message:
|
||||
'Missing workspace, user, blob id, or copilot context for blob_read.',
|
||||
});
|
||||
});
|
||||
|
||||
@@ -470,8 +470,7 @@ export abstract class CopilotProvider<C = any> {
|
||||
});
|
||||
const searchDocs = buildDocKeywordSearchGetter(
|
||||
ac,
|
||||
indexerService,
|
||||
models
|
||||
indexerService
|
||||
);
|
||||
tools.doc_keyword_search = createDocKeywordSearchTool(
|
||||
searchDocs.bind(null, options)
|
||||
|
||||
@@ -18,10 +18,7 @@ export const buildBlobContentGetter = (
|
||||
chunk?: number
|
||||
) => {
|
||||
if (!options?.user || !options?.workspace || !blobId || !context) {
|
||||
return toolError(
|
||||
'Blob Read Failed',
|
||||
'Missing workspace, user, blob id, or copilot context for blob_read.'
|
||||
);
|
||||
return;
|
||||
}
|
||||
const canAccess = await ac
|
||||
.user(options.user)
|
||||
@@ -32,10 +29,7 @@ export const buildBlobContentGetter = (
|
||||
logger.warn(
|
||||
`User ${options.user} does not have access workspace ${options.workspace}`
|
||||
);
|
||||
return toolError(
|
||||
'Blob Read Failed',
|
||||
'You do not have permission to access this workspace attachment.'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const contextFile = context.files.find(
|
||||
@@ -48,12 +42,7 @@ export const buildBlobContentGetter = (
|
||||
context.getBlobContent(canonicalBlobId, chunk),
|
||||
]);
|
||||
const content = file?.trim() || blob?.trim();
|
||||
if (!content) {
|
||||
return toolError(
|
||||
'Blob Read Failed',
|
||||
`Attachment ${canonicalBlobId} is not available for reading in the current copilot context.`
|
||||
);
|
||||
}
|
||||
if (!content) return;
|
||||
const info = contextFile
|
||||
? { fileName: contextFile.name, fileType: contextFile.mimeType }
|
||||
: {};
|
||||
@@ -64,7 +53,10 @@ export const buildBlobContentGetter = (
|
||||
};
|
||||
|
||||
export const createBlobReadTool = (
|
||||
getBlobContent: (targetId?: string, chunk?: number) => Promise<object>
|
||||
getBlobContent: (
|
||||
targetId?: string,
|
||||
chunk?: number
|
||||
) => Promise<object | undefined>
|
||||
) => {
|
||||
return defineTool({
|
||||
description:
|
||||
@@ -81,10 +73,13 @@ export const createBlobReadTool = (
|
||||
execute: async ({ blob_id, chunk }) => {
|
||||
try {
|
||||
const blob = await getBlobContent(blob_id, chunk);
|
||||
if (!blob) {
|
||||
return;
|
||||
}
|
||||
return { ...blob };
|
||||
} catch (err: any) {
|
||||
logger.error(`Failed to read the blob ${blob_id} in context`, err);
|
||||
return toolError('Blob Read Failed', err.message ?? String(err));
|
||||
return toolError('Blob Read Failed', err.message);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,43 +1,27 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import type { AccessController } from '../../../core/permission';
|
||||
import type { Models } from '../../../models';
|
||||
import type { IndexerService, SearchDoc } from '../../indexer';
|
||||
import { workspaceSyncRequiredError } from './doc-sync';
|
||||
import { toolError } from './error';
|
||||
import { defineTool } from './tool';
|
||||
import type { CopilotChatOptions } from './types';
|
||||
|
||||
export const buildDocKeywordSearchGetter = (
|
||||
ac: AccessController,
|
||||
indexerService: IndexerService,
|
||||
models: Models
|
||||
indexerService: IndexerService
|
||||
) => {
|
||||
const searchDocs = async (options: CopilotChatOptions, query?: string) => {
|
||||
const queryTrimmed = query?.trim();
|
||||
if (!options || !queryTrimmed || !options.user || !options.workspace) {
|
||||
return toolError(
|
||||
'Doc Keyword Search Failed',
|
||||
'Missing workspace, user, or query for doc_keyword_search.'
|
||||
);
|
||||
}
|
||||
const workspace = await models.workspace.get(options.workspace);
|
||||
if (!workspace) {
|
||||
return workspaceSyncRequiredError();
|
||||
if (!options || !query?.trim() || !options.user || !options.workspace) {
|
||||
return undefined;
|
||||
}
|
||||
const canAccess = await ac
|
||||
.user(options.user)
|
||||
.workspace(options.workspace)
|
||||
.can('Workspace.Read');
|
||||
if (!canAccess) {
|
||||
return toolError(
|
||||
'Doc Keyword Search Failed',
|
||||
'You do not have permission to access this workspace.'
|
||||
);
|
||||
}
|
||||
if (!canAccess) return undefined;
|
||||
const docs = await indexerService.searchDocsByKeyword(
|
||||
options.workspace,
|
||||
queryTrimmed
|
||||
query
|
||||
);
|
||||
|
||||
// filter current user readable docs
|
||||
@@ -45,15 +29,13 @@ export const buildDocKeywordSearchGetter = (
|
||||
.user(options.user)
|
||||
.workspace(options.workspace)
|
||||
.docs(docs, 'Doc.Read');
|
||||
return readableDocs ?? [];
|
||||
return readableDocs;
|
||||
};
|
||||
return searchDocs;
|
||||
};
|
||||
|
||||
export const createDocKeywordSearchTool = (
|
||||
searchDocs: (
|
||||
query: string
|
||||
) => Promise<SearchDoc[] | ReturnType<typeof toolError>>
|
||||
searchDocs: (query: string) => Promise<SearchDoc[] | undefined>
|
||||
) => {
|
||||
return defineTool({
|
||||
description:
|
||||
@@ -68,8 +50,8 @@ export const createDocKeywordSearchTool = (
|
||||
execute: async ({ query }) => {
|
||||
try {
|
||||
const docs = await searchDocs(query);
|
||||
if (!Array.isArray(docs)) {
|
||||
return docs;
|
||||
if (!docs) {
|
||||
return;
|
||||
}
|
||||
return docs.map(doc => ({
|
||||
docId: doc.docId,
|
||||
|
||||
@@ -3,20 +3,13 @@ import { z } from 'zod';
|
||||
|
||||
import { DocReader } from '../../../core/doc';
|
||||
import { AccessController } from '../../../core/permission';
|
||||
import { Models } from '../../../models';
|
||||
import {
|
||||
documentSyncPendingError,
|
||||
workspaceSyncRequiredError,
|
||||
} from './doc-sync';
|
||||
import { type ToolError, toolError } from './error';
|
||||
import { Models, publicUserSelect } from '../../../models';
|
||||
import { toolError } from './error';
|
||||
import { defineTool } from './tool';
|
||||
import type { CopilotChatOptions } from './types';
|
||||
|
||||
const logger = new Logger('DocReadTool');
|
||||
|
||||
const isToolError = (result: ToolError | object): result is ToolError =>
|
||||
'type' in result && result.type === 'error';
|
||||
|
||||
export const buildDocContentGetter = (
|
||||
ac: AccessController,
|
||||
docReader: DocReader,
|
||||
@@ -24,17 +17,8 @@ export const buildDocContentGetter = (
|
||||
) => {
|
||||
const getDoc = async (options: CopilotChatOptions, docId?: string) => {
|
||||
if (!options?.user || !options?.workspace || !docId) {
|
||||
return toolError(
|
||||
'Doc Read Failed',
|
||||
'Missing workspace, user, or document id for doc_read.'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const workspace = await models.workspace.get(options.workspace);
|
||||
if (!workspace) {
|
||||
return workspaceSyncRequiredError();
|
||||
}
|
||||
|
||||
const canAccess = await ac
|
||||
.user(options.user)
|
||||
.workspace(options.workspace)
|
||||
@@ -44,15 +28,23 @@ export const buildDocContentGetter = (
|
||||
logger.warn(
|
||||
`User ${options.user} does not have access to doc ${docId} in workspace ${options.workspace}`
|
||||
);
|
||||
return toolError(
|
||||
'Doc Read Failed',
|
||||
`You do not have permission to read document ${docId} in this workspace.`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const docMeta = await models.doc.getAuthors(options.workspace, docId);
|
||||
const docMeta = await models.doc.getSnapshot(options.workspace, docId, {
|
||||
select: {
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
createdByUser: {
|
||||
select: publicUserSelect,
|
||||
},
|
||||
updatedByUser: {
|
||||
select: publicUserSelect,
|
||||
},
|
||||
},
|
||||
});
|
||||
if (!docMeta) {
|
||||
return documentSyncPendingError(docId);
|
||||
return;
|
||||
}
|
||||
|
||||
const content = await docReader.getDocMarkdown(
|
||||
@@ -61,7 +53,7 @@ export const buildDocContentGetter = (
|
||||
true
|
||||
);
|
||||
if (!content) {
|
||||
return documentSyncPendingError(docId);
|
||||
return;
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -77,12 +69,8 @@ export const buildDocContentGetter = (
|
||||
return getDoc;
|
||||
};
|
||||
|
||||
type DocReadToolResult = Awaited<
|
||||
ReturnType<ReturnType<typeof buildDocContentGetter>>
|
||||
>;
|
||||
|
||||
export const createDocReadTool = (
|
||||
getDoc: (targetId?: string) => Promise<DocReadToolResult>
|
||||
getDoc: (targetId?: string) => Promise<object | undefined>
|
||||
) => {
|
||||
return defineTool({
|
||||
description:
|
||||
@@ -93,10 +81,13 @@ export const createDocReadTool = (
|
||||
execute: async ({ doc_id }) => {
|
||||
try {
|
||||
const doc = await getDoc(doc_id);
|
||||
return isToolError(doc) ? doc : { ...doc };
|
||||
if (!doc) {
|
||||
return;
|
||||
}
|
||||
return { ...doc };
|
||||
} catch (err: any) {
|
||||
logger.error(`Failed to read the doc ${doc_id}`, err);
|
||||
return toolError('Doc Read Failed', err.message ?? String(err));
|
||||
return toolError('Doc Read Failed', err.message);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -7,7 +7,6 @@ import {
|
||||
clearEmbeddingChunk,
|
||||
type Models,
|
||||
} from '../../../models';
|
||||
import { workspaceSyncRequiredError } from './doc-sync';
|
||||
import { toolError } from './error';
|
||||
import { defineTool } from './tool';
|
||||
import type {
|
||||
@@ -28,24 +27,14 @@ export const buildDocSearchGetter = (
|
||||
signal?: AbortSignal
|
||||
) => {
|
||||
if (!options || !query?.trim() || !options.user || !options.workspace) {
|
||||
return toolError(
|
||||
'Doc Semantic Search Failed',
|
||||
'Missing workspace, user, or query for doc_semantic_search.'
|
||||
);
|
||||
}
|
||||
const workspace = await models.workspace.get(options.workspace);
|
||||
if (!workspace) {
|
||||
return workspaceSyncRequiredError();
|
||||
return `Invalid search parameters.`;
|
||||
}
|
||||
const canAccess = await ac
|
||||
.user(options.user)
|
||||
.workspace(options.workspace)
|
||||
.can('Workspace.Read');
|
||||
if (!canAccess)
|
||||
return toolError(
|
||||
'Doc Semantic Search Failed',
|
||||
'You do not have permission to access this workspace.'
|
||||
);
|
||||
return 'You do not have permission to access this workspace.';
|
||||
const [chunks, contextChunks] = await Promise.all([
|
||||
context.matchWorkspaceAll(options.workspace, query, 10, signal),
|
||||
docContext?.matchFiles(query, 10, signal) ?? [],
|
||||
@@ -64,7 +53,7 @@ export const buildDocSearchGetter = (
|
||||
fileChunks.push(...contextChunks);
|
||||
}
|
||||
if (!blobChunks.length && !docChunks.length && !fileChunks.length) {
|
||||
return [];
|
||||
return `No results found for "${query}".`;
|
||||
}
|
||||
|
||||
const docIds = docChunks.map(c => ({
|
||||
@@ -112,7 +101,7 @@ export const createDocSemanticSearchTool = (
|
||||
searchDocs: (
|
||||
query: string,
|
||||
signal?: AbortSignal
|
||||
) => Promise<ChunkSimilarity[] | ReturnType<typeof toolError>>
|
||||
) => Promise<ChunkSimilarity[] | string | undefined>
|
||||
) => {
|
||||
return defineTool({
|
||||
description:
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
import { toolError } from './error';
|
||||
|
||||
export const LOCAL_WORKSPACE_SYNC_REQUIRED_MESSAGE =
|
||||
'This workspace is local-only and does not have AFFiNE Cloud sync enabled yet. Ask the user to enable workspace sync, then try again.';
|
||||
|
||||
export const DOCUMENT_SYNC_PENDING_MESSAGE = (docId: string) =>
|
||||
`Document ${docId} is not available on AFFiNE Cloud yet. Ask the user to wait for workspace sync to finish, then try again.`;
|
||||
|
||||
export const workspaceSyncRequiredError = () =>
|
||||
toolError('Workspace Sync Required', LOCAL_WORKSPACE_SYNC_REQUIRED_MESSAGE);
|
||||
|
||||
export const documentSyncPendingError = (docId: string) =>
|
||||
toolError('Document Sync Pending', DOCUMENT_SYNC_PENDING_MESSAGE(docId));
|
||||
@@ -32,8 +32,8 @@
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/Lakr233/ListViewKit",
|
||||
"state" : {
|
||||
"revision" : "07f7adfa0629f8647991e3c148b7d3e060fe2917",
|
||||
"version" : "1.2.0"
|
||||
"revision" : "5dea05a52a6c2c7bb013a5925c517d6e32940605",
|
||||
"version" : "1.1.8"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -59,8 +59,8 @@
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/Lakr233/MarkdownView",
|
||||
"state" : {
|
||||
"revision" : "37f97345a108e95f66b6671c317b43063c7f2de1",
|
||||
"version" : "3.8.2"
|
||||
"revision" : "8b8c1eecd251051c5ec2bdd5f31a2243efd9be6c",
|
||||
"version" : "3.6.2"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -21,8 +21,8 @@ let package = Package(
|
||||
.package(url: "https://github.com/SnapKit/SnapKit.git", from: "5.7.1"),
|
||||
.package(url: "https://github.com/SwifterSwift/SwifterSwift.git", from: "6.2.0"),
|
||||
.package(url: "https://github.com/Recouse/EventSource.git", from: "0.1.7"),
|
||||
.package(url: "https://github.com/Lakr233/ListViewKit.git", from: "1.2.0"),
|
||||
.package(url: "https://github.com/Lakr233/MarkdownView.git", from: "3.8.2"),
|
||||
.package(url: "https://github.com/Lakr233/ListViewKit.git", from: "1.1.8"),
|
||||
.package(url: "https://github.com/Lakr233/MarkdownView.git", from: "3.6.3"),
|
||||
],
|
||||
targets: [
|
||||
.target(name: "Intelligents", dependencies: [
|
||||
|
||||
@@ -7,8 +7,6 @@ import { html, nothing } from 'lit';
|
||||
import { property } from 'lit/decorators.js';
|
||||
|
||||
import type { ToolResult } from './tool-result-card';
|
||||
import { getToolErrorDisplayName, isToolError } from './tool-result-utils';
|
||||
import type { ToolError } from './type';
|
||||
|
||||
interface DocKeywordSearchToolCall {
|
||||
type: 'tool-call';
|
||||
@@ -22,7 +20,10 @@ interface DocKeywordSearchToolResult {
|
||||
toolCallId: string;
|
||||
toolName: string;
|
||||
args: { query: string };
|
||||
result: Array<{ title: string; docId: string }> | ToolError | null;
|
||||
result: Array<{
|
||||
title: string;
|
||||
docId: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
export class DocKeywordSearchResult extends WithDisposable(ShadowlessElement) {
|
||||
@@ -50,23 +51,9 @@ export class DocKeywordSearchResult extends WithDisposable(ShadowlessElement) {
|
||||
if (this.data.type !== 'tool-result') {
|
||||
return nothing;
|
||||
}
|
||||
const result = this.data.result;
|
||||
if (!result || isToolError(result)) {
|
||||
return html`<tool-call-failed
|
||||
.name=${getToolErrorDisplayName(
|
||||
isToolError(result) ? result : null,
|
||||
'Document search failed',
|
||||
{
|
||||
'Workspace Sync Required':
|
||||
'Enable workspace sync to search documents',
|
||||
}
|
||||
)}
|
||||
.icon=${SearchIcon()}
|
||||
></tool-call-failed>`;
|
||||
}
|
||||
let results: ToolResult[] = [];
|
||||
try {
|
||||
results = result.map(item => ({
|
||||
results = this.data.result.map(item => ({
|
||||
title: item.title,
|
||||
icon: PageIcon(),
|
||||
onClick: () => {
|
||||
@@ -82,7 +69,7 @@ export class DocKeywordSearchResult extends WithDisposable(ShadowlessElement) {
|
||||
console.error('Failed to parse result', err);
|
||||
}
|
||||
return html`<tool-result-card
|
||||
.name=${`Found ${result.length} pages for "${this.data.args.query}"`}
|
||||
.name=${`Found ${this.data.result.length} pages for "${this.data.args.query}"`}
|
||||
.icon=${SearchIcon()}
|
||||
.width=${this.width}
|
||||
.results=${results}
|
||||
|
||||
@@ -6,9 +6,6 @@ import type { Signal } from '@preact/signals-core';
|
||||
import { html, nothing } from 'lit';
|
||||
import { property } from 'lit/decorators.js';
|
||||
|
||||
import { getToolErrorDisplayName, isToolError } from './tool-result-utils';
|
||||
import type { ToolError } from './type';
|
||||
|
||||
interface DocReadToolCall {
|
||||
type: 'tool-call';
|
||||
toolCallId: string;
|
||||
@@ -21,24 +18,14 @@ interface DocReadToolResult {
|
||||
toolCallId: string;
|
||||
toolName: string;
|
||||
args: { doc_id: string };
|
||||
result:
|
||||
| {
|
||||
/** Old result may not have docId */
|
||||
docId?: string;
|
||||
title: string;
|
||||
markdown: string;
|
||||
}
|
||||
| ToolError
|
||||
| null;
|
||||
result: {
|
||||
/** Old result may not have docId */
|
||||
docId?: string;
|
||||
title: string;
|
||||
markdown: string;
|
||||
};
|
||||
}
|
||||
|
||||
const getFailedName = (result: ToolError | null) => {
|
||||
return getToolErrorDisplayName(result, 'Document read failed', {
|
||||
'Workspace Sync Required': 'Enable workspace sync to read this document',
|
||||
'Document Sync Pending': 'Wait for document sync to finish',
|
||||
});
|
||||
};
|
||||
|
||||
export class DocReadResult extends WithDisposable(ShadowlessElement) {
|
||||
@property({ attribute: false })
|
||||
accessor data!: DocReadToolCall | DocReadToolResult;
|
||||
@@ -62,25 +49,18 @@ export class DocReadResult extends WithDisposable(ShadowlessElement) {
|
||||
if (this.data.type !== 'tool-result') {
|
||||
return nothing;
|
||||
}
|
||||
const result = this.data.result;
|
||||
if (!result || isToolError(result)) {
|
||||
return html`<tool-call-failed
|
||||
.name=${getFailedName(isToolError(result) ? result : null)}
|
||||
.icon=${ViewIcon()}
|
||||
></tool-call-failed>`;
|
||||
}
|
||||
// TODO: better markdown rendering
|
||||
return html`<tool-result-card
|
||||
.name=${`Read "${result.title}"`}
|
||||
.name=${`Read "${this.data.result.title}"`}
|
||||
.icon=${ViewIcon()}
|
||||
.width=${this.width}
|
||||
.results=${[
|
||||
{
|
||||
title: result.title,
|
||||
title: this.data.result.title,
|
||||
icon: PageIcon(),
|
||||
content: result.markdown,
|
||||
content: this.data.result.markdown,
|
||||
onClick: () => {
|
||||
const docId = result.docId;
|
||||
const docId = (this.data as DocReadToolResult).result.docId;
|
||||
if (!docId) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -7,8 +7,6 @@ import { html, nothing } from 'lit';
|
||||
import { property } from 'lit/decorators.js';
|
||||
|
||||
import type { DocDisplayConfig } from '../ai-chat-chips';
|
||||
import { getToolErrorDisplayName, isToolError } from './tool-result-utils';
|
||||
import type { ToolError } from './type';
|
||||
|
||||
interface DocSemanticSearchToolCall {
|
||||
type: 'tool-call';
|
||||
@@ -22,7 +20,10 @@ interface DocSemanticSearchToolResult {
|
||||
toolCallId: string;
|
||||
toolName: string;
|
||||
args: { query: string };
|
||||
result: Array<{ content: string; docId: string }> | ToolError | null;
|
||||
result: Array<{
|
||||
content: string;
|
||||
docId: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
function parseResultContent(content: string) {
|
||||
@@ -81,25 +82,11 @@ export class DocSemanticSearchResult extends WithDisposable(ShadowlessElement) {
|
||||
if (this.data.type !== 'tool-result') {
|
||||
return nothing;
|
||||
}
|
||||
const result = this.data.result;
|
||||
if (!result || isToolError(result)) {
|
||||
return html`<tool-call-failed
|
||||
.name=${getToolErrorDisplayName(
|
||||
isToolError(result) ? result : null,
|
||||
'Semantic search failed',
|
||||
{
|
||||
'Workspace Sync Required':
|
||||
'Enable workspace sync to search documents',
|
||||
}
|
||||
)}
|
||||
.icon=${AiEmbeddingIcon()}
|
||||
></tool-call-failed>`;
|
||||
}
|
||||
return html`<tool-result-card
|
||||
.name=${`Found semantically related pages for "${this.data.args.query}"`}
|
||||
.icon=${AiEmbeddingIcon()}
|
||||
.width=${this.width}
|
||||
.results=${result
|
||||
.results=${this.data.result
|
||||
.map(result => ({
|
||||
...parseResultContent(result.content),
|
||||
title: this.docDisplayService.getTitle(result.docId),
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
import type { ToolError } from './type';
|
||||
|
||||
export const isToolError = (result: unknown): result is ToolError =>
|
||||
!!result &&
|
||||
typeof result === 'object' &&
|
||||
'type' in result &&
|
||||
(result as ToolError).type === 'error';
|
||||
|
||||
export const getToolErrorDisplayName = (
|
||||
result: ToolError | null,
|
||||
fallback: string,
|
||||
overrides: Record<string, string> = {}
|
||||
) => {
|
||||
if (!result) return fallback;
|
||||
return overrides[result.name] ?? result.name;
|
||||
};
|
||||
@@ -54,22 +54,13 @@ export class I18n extends Entity {
|
||||
constructor(private readonly cache: GlobalCache) {
|
||||
super();
|
||||
this.i18n.on('languageChanged', (language: Language) => {
|
||||
this.applyDocumentLanguage(language);
|
||||
document.documentElement.lang = language;
|
||||
this.cache.set('i18n_lng', language);
|
||||
});
|
||||
}
|
||||
|
||||
init() {
|
||||
const language = this.currentLanguageKey$.value ?? 'en';
|
||||
this.applyDocumentLanguage(language);
|
||||
this.changeLanguage(language);
|
||||
}
|
||||
|
||||
private applyDocumentLanguage(language: Language) {
|
||||
document.documentElement.lang = language;
|
||||
document.documentElement.dir = SUPPORTED_LANGUAGES[language]?.rtl
|
||||
? 'rtl'
|
||||
: 'ltr';
|
||||
this.changeLanguage(this.currentLanguageKey$.value ?? 'en');
|
||||
}
|
||||
|
||||
changeLanguage = effect(
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -32,7 +32,6 @@ export const SUPPORTED_LANGUAGES: Record<
|
||||
name: string;
|
||||
originalName: string;
|
||||
flagEmoji: string;
|
||||
rtl?: boolean;
|
||||
resource:
|
||||
| LanguageResource
|
||||
| (() => Promise<{ default: Partial<LanguageResource> }>);
|
||||
@@ -150,21 +149,18 @@ export const SUPPORTED_LANGUAGES: Record<
|
||||
name: 'Urdu',
|
||||
originalName: 'اردو',
|
||||
flagEmoji: '🇵🇰',
|
||||
rtl: true,
|
||||
resource: () => import('./ur.json'),
|
||||
},
|
||||
ar: {
|
||||
name: 'Arabic',
|
||||
originalName: 'العربية',
|
||||
flagEmoji: '🇸🇦',
|
||||
rtl: true,
|
||||
resource: () => import('./ar.json'),
|
||||
},
|
||||
fa: {
|
||||
name: 'Persian',
|
||||
originalName: 'فارسی',
|
||||
flagEmoji: '🇮🇷',
|
||||
rtl: true,
|
||||
resource: () => import('./fa.json'),
|
||||
},
|
||||
uk: {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!doctype html>
|
||||
<html lang="en" dir="ltr">
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta
|
||||
|
||||
251
yarn.lock
251
yarn.lock
@@ -6139,15 +6139,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@graphql-tools/merge@npm:9.1.7, @graphql-tools/merge@npm:^9.0.0, @graphql-tools/merge@npm:^9.1.7":
|
||||
version: 9.1.7
|
||||
resolution: "@graphql-tools/merge@npm:9.1.7"
|
||||
"@graphql-tools/merge@npm:9.0.24, @graphql-tools/merge@npm:^9.0.0, @graphql-tools/merge@npm:^9.0.24":
|
||||
version: 9.0.24
|
||||
resolution: "@graphql-tools/merge@npm:9.0.24"
|
||||
dependencies:
|
||||
"@graphql-tools/utils": "npm:^11.0.0"
|
||||
"@graphql-tools/utils": "npm:^10.8.6"
|
||||
tslib: "npm:^2.4.0"
|
||||
peerDependencies:
|
||||
graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0
|
||||
checksum: 10/e0b77dfc16e91d7c2450df0b57a85a93e11f0f67e37e396bcf04275d1db8ed1b7257c763ebe6e7f122041d81f00d6aa954fbec531fa6c0b449d195a9aff199cc
|
||||
checksum: 10/95f77ff141f10d5d726cd8d1ae1ad84ed944c84346bf20461adca9b1543bb94cb524b0347885fe61d3158ccf5ffe1dddec361787ae40bfcc3449aad51528dd77
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -6213,16 +6213,16 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@graphql-tools/schema@npm:10.0.31, @graphql-tools/schema@npm:^10.0.0, @graphql-tools/schema@npm:^10.0.11, @graphql-tools/schema@npm:^10.0.23":
|
||||
version: 10.0.31
|
||||
resolution: "@graphql-tools/schema@npm:10.0.31"
|
||||
"@graphql-tools/schema@npm:10.0.23, @graphql-tools/schema@npm:^10.0.0, @graphql-tools/schema@npm:^10.0.11, @graphql-tools/schema@npm:^10.0.23":
|
||||
version: 10.0.23
|
||||
resolution: "@graphql-tools/schema@npm:10.0.23"
|
||||
dependencies:
|
||||
"@graphql-tools/merge": "npm:^9.1.7"
|
||||
"@graphql-tools/utils": "npm:^11.0.0"
|
||||
"@graphql-tools/merge": "npm:^9.0.24"
|
||||
"@graphql-tools/utils": "npm:^10.8.6"
|
||||
tslib: "npm:^2.4.0"
|
||||
peerDependencies:
|
||||
graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0
|
||||
checksum: 10/5b775736f8b8454319e07cadc7d41bc5c9cc804a393490aaffd1bb7f59afddb02b498837e870bf98db4a1a989a721d5d8e2fd2b97409d078bac14503c2d4f9cb
|
||||
checksum: 10/f0960dae161a478941276df1802af09844825c8135e4695b36f5f7e7384f43ff8e1288a67546023fc861951d783327f239112ccf563cb4be1f22038fc78acf21
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -6262,21 +6262,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@graphql-tools/utils@npm:11.0.0, @graphql-tools/utils@npm:^11.0.0":
|
||||
version: 11.0.0
|
||||
resolution: "@graphql-tools/utils@npm:11.0.0"
|
||||
dependencies:
|
||||
"@graphql-typed-document-node/core": "npm:^3.1.1"
|
||||
"@whatwg-node/promise-helpers": "npm:^1.0.0"
|
||||
cross-inspect: "npm:1.0.1"
|
||||
tslib: "npm:^2.4.0"
|
||||
peerDependencies:
|
||||
graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0
|
||||
checksum: 10/4cc7577ab85d60908a1d5d448071b318791b798f571cd4b8e4289e0e0eeae9d7183b661a1a7d5da3cedaf5f9b62b936031e3a90d2e17a1c50acbd95d9106ba3c
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@graphql-tools/utils@npm:^10.0.0, @graphql-tools/utils@npm:^10.5.6, @graphql-tools/utils@npm:^10.8.1, @graphql-tools/utils@npm:^10.8.6":
|
||||
"@graphql-tools/utils@npm:10.8.6, @graphql-tools/utils@npm:^10.0.0, @graphql-tools/utils@npm:^10.5.6, @graphql-tools/utils@npm:^10.8.1, @graphql-tools/utils@npm:^10.8.6":
|
||||
version: 10.8.6
|
||||
resolution: "@graphql-tools/utils@npm:10.8.6"
|
||||
dependencies:
|
||||
@@ -7554,10 +7540,10 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@microsoft/tsdoc@npm:0.16.0":
|
||||
version: 0.16.0
|
||||
resolution: "@microsoft/tsdoc@npm:0.16.0"
|
||||
checksum: 10/1eaad3605234dc7e44898c15d1ba3c97fb968af1117025400cba572ce268da05afc36634d1fb9e779457af3ff7f13330aee07a962510a4d9c6612c13f71ee41e
|
||||
"@microsoft/tsdoc@npm:0.15.1":
|
||||
version: 0.15.1
|
||||
resolution: "@microsoft/tsdoc@npm:0.15.1"
|
||||
checksum: 10/1a92612883088fe184dba596e7ba7a0daef0e6981caeca22bad6ad551d2247294f12e368537d0d8192525cf5743f7f15fcc2ad7b3b849f26a09a15ffdd89fd0c
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -8811,8 +8797,8 @@ __metadata:
|
||||
linkType: hard
|
||||
|
||||
"@nestjs/apollo@npm:^13.0.4":
|
||||
version: 13.2.4
|
||||
resolution: "@nestjs/apollo@npm:13.2.4"
|
||||
version: 13.1.0
|
||||
resolution: "@nestjs/apollo@npm:13.1.0"
|
||||
dependencies:
|
||||
"@apollo/server-plugin-landing-page-graphql-playground": "npm:4.0.1"
|
||||
iterall: "npm:1.3.0"
|
||||
@@ -8820,9 +8806,9 @@ __metadata:
|
||||
tslib: "npm:2.8.1"
|
||||
peerDependencies:
|
||||
"@apollo/gateway": ^2.0.0
|
||||
"@apollo/server": ^5.0.0
|
||||
"@apollo/server": ^4.11.3
|
||||
"@apollo/subgraph": ^2.0.0
|
||||
"@as-integrations/fastify": ^2.1.1 || ^3.0.0
|
||||
"@as-integrations/fastify": ^2.1.1
|
||||
"@nestjs/common": ^11.0.1
|
||||
"@nestjs/core": ^11.0.1
|
||||
"@nestjs/graphql": ^13.0.0
|
||||
@@ -8832,11 +8818,9 @@ __metadata:
|
||||
optional: true
|
||||
"@apollo/subgraph":
|
||||
optional: true
|
||||
"@as-integrations/express5":
|
||||
optional: true
|
||||
"@as-integrations/fastify":
|
||||
optional: true
|
||||
checksum: 10/f4500f1026096dc6f98cddd6f82eb2afc8ac22b6e145388477e1391c193e4bc2fd9f05a7b32b77ebfea044f7995cf2bc99d2df189be66f7d62d62bb5e3ff13d7
|
||||
checksum: 10/c78e409187ef905428429bea3ebb8dff450df7cc95915f0692fec0eb531e3d54b3f7ab56492fb627a9124f347d1740b4b4f35825d45cdd3e5aa5ae0692e59089
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -8867,10 +8851,10 @@ __metadata:
|
||||
linkType: hard
|
||||
|
||||
"@nestjs/common@npm:^11.0.21":
|
||||
version: 11.1.17
|
||||
resolution: "@nestjs/common@npm:11.1.17"
|
||||
version: 11.1.14
|
||||
resolution: "@nestjs/common@npm:11.1.14"
|
||||
dependencies:
|
||||
file-type: "npm:21.3.2"
|
||||
file-type: "npm:21.3.0"
|
||||
iterare: "npm:1.2.1"
|
||||
load-esm: "npm:1.0.3"
|
||||
tslib: "npm:2.8.1"
|
||||
@@ -8885,13 +8869,13 @@ __metadata:
|
||||
optional: true
|
||||
class-validator:
|
||||
optional: true
|
||||
checksum: 10/5cf09b83373ac1090a3f81ffcf34b91237f062b7e48ff37453e7c2d0cb4373e2e8dc33ed13636ff0e944a900ab19508436a76c8a5e73702d57f930ed5a0cc9c2
|
||||
checksum: 10/420c4f66ce4d3219196fa9cc6a6c4de63aacdabe869cc95754419a0d306f498a1e52031cf2f5f6c1737616943323309fb875708f5a4f61c960eebc62d5f60232
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@nestjs/core@npm:^11.1.14":
|
||||
version: 11.1.17
|
||||
resolution: "@nestjs/core@npm:11.1.17"
|
||||
version: 11.1.14
|
||||
resolution: "@nestjs/core@npm:11.1.14"
|
||||
dependencies:
|
||||
"@nuxt/opencollective": "npm:0.4.1"
|
||||
fast-safe-stringify: "npm:2.1.1"
|
||||
@@ -8913,36 +8897,36 @@ __metadata:
|
||||
optional: true
|
||||
"@nestjs/websockets":
|
||||
optional: true
|
||||
checksum: 10/287e14b4d4a662071c9ad41d8f864010561108241da4c2b9df2f7d41962a0a9e5be6e5bcdf9b04087955ef62b7ff56f4c892da25827566dbe5bd06a921c39f8a
|
||||
checksum: 10/e346efe760079c99ab303bf05de587f172d348c194a174a4665944ab6858b36993a64b07afe62772c4e9042c685797d02579b86f12d549eb199842b436c9045e
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@nestjs/graphql@npm:^13.0.4":
|
||||
version: 13.2.4
|
||||
resolution: "@nestjs/graphql@npm:13.2.4"
|
||||
version: 13.1.0
|
||||
resolution: "@nestjs/graphql@npm:13.1.0"
|
||||
dependencies:
|
||||
"@graphql-tools/merge": "npm:9.1.7"
|
||||
"@graphql-tools/schema": "npm:10.0.31"
|
||||
"@graphql-tools/utils": "npm:11.0.0"
|
||||
"@graphql-tools/merge": "npm:9.0.24"
|
||||
"@graphql-tools/schema": "npm:10.0.23"
|
||||
"@graphql-tools/utils": "npm:10.8.6"
|
||||
"@nestjs/mapped-types": "npm:2.1.0"
|
||||
chokidar: "npm:4.0.3"
|
||||
fast-glob: "npm:3.3.3"
|
||||
graphql-tag: "npm:2.12.6"
|
||||
graphql-ws: "npm:6.0.7"
|
||||
lodash: "npm:4.17.23"
|
||||
graphql-ws: "npm:6.0.4"
|
||||
lodash: "npm:4.17.21"
|
||||
normalize-path: "npm:3.0.0"
|
||||
subscriptions-transport-ws: "npm:0.11.0"
|
||||
tslib: "npm:2.8.1"
|
||||
ws: "npm:8.19.0"
|
||||
ws: "npm:8.18.1"
|
||||
peerDependencies:
|
||||
"@apollo/subgraph": ^2.9.3
|
||||
"@nestjs/common": ^11.0.1
|
||||
"@nestjs/core": ^11.0.1
|
||||
class-transformer: "*"
|
||||
class-validator: "*"
|
||||
graphql: ^16.11.0
|
||||
graphql: ^16.10.0
|
||||
reflect-metadata: ^0.1.13 || ^0.2.0
|
||||
ts-morph: ^20.0.0 || ^21.0.0 || ^24.0.0 || ^25.0.0 || ^26.0.0 || ^27.0.0
|
||||
ts-morph: ^20.0.0 || ^21.0.0 || ^24.0.0 || ^25.0.0
|
||||
peerDependenciesMeta:
|
||||
"@apollo/subgraph":
|
||||
optional: true
|
||||
@@ -8952,7 +8936,7 @@ __metadata:
|
||||
optional: true
|
||||
ts-morph:
|
||||
optional: true
|
||||
checksum: 10/72b17b34b3915fddc7c0f882d09a60850b3571aabeb19c4f811f91f393e4f04bf4c5c53133cf7ed210353d03ece7d1a615e60ea19905ca137b3e317b66094704
|
||||
checksum: 10/07d860f665465243d6b0486fae3fed6d33a8057f6bbe982989943a288717468cba183b31cb290eecc28dd0d8c83764a0f62215e4c4fdf5967bb3c176a62d4873
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -8974,24 +8958,24 @@ __metadata:
|
||||
linkType: hard
|
||||
|
||||
"@nestjs/platform-express@npm:^11.1.14":
|
||||
version: 11.1.17
|
||||
resolution: "@nestjs/platform-express@npm:11.1.17"
|
||||
version: 11.1.14
|
||||
resolution: "@nestjs/platform-express@npm:11.1.14"
|
||||
dependencies:
|
||||
cors: "npm:2.8.6"
|
||||
express: "npm:5.2.1"
|
||||
multer: "npm:2.1.1"
|
||||
multer: "npm:2.0.2"
|
||||
path-to-regexp: "npm:8.3.0"
|
||||
tslib: "npm:2.8.1"
|
||||
peerDependencies:
|
||||
"@nestjs/common": ^11.0.0
|
||||
"@nestjs/core": ^11.0.0
|
||||
checksum: 10/a5ed648f6797ebd153299c71b07d0ffc83f7891bc88d8a13fca5de77d772ee83ba45bfc9c7a6ba72496f3f078025b32d7f793ea7088542ef142cd42fe12cdd9c
|
||||
checksum: 10/748259454b89a1e8d9764a8947839a67f2a608eea47ee5e978051f57626bbedfb20fe31b47f4f152e0d72316f37ecb0196e1889940ce33836db13316ca794b4f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@nestjs/platform-socket.io@npm:^11.1.14":
|
||||
version: 11.1.17
|
||||
resolution: "@nestjs/platform-socket.io@npm:11.1.17"
|
||||
version: 11.1.14
|
||||
resolution: "@nestjs/platform-socket.io@npm:11.1.14"
|
||||
dependencies:
|
||||
socket.io: "npm:4.8.3"
|
||||
tslib: "npm:2.8.1"
|
||||
@@ -8999,7 +8983,7 @@ __metadata:
|
||||
"@nestjs/common": ^11.0.0
|
||||
"@nestjs/websockets": ^11.0.0
|
||||
rxjs: ^7.1.0
|
||||
checksum: 10/909f81d766b1187e1353951e37f61eb4f3181d9f9df65ac1316fdca853acda62650b7c5e2d8fe228fd2586ffc6a1ee4697c172e8762371aae3315ecf0093d2c3
|
||||
checksum: 10/b9e8f28c9ef906f498a412bbcf7d47ee360bcc2d6805cebc234706211e0f6b5bb411bb7bc2676a1aafa0e705777b49779703b4d74da804ba0e0b86ca6189ba74
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -9016,17 +9000,17 @@ __metadata:
|
||||
linkType: hard
|
||||
|
||||
"@nestjs/swagger@npm:^11.2.0":
|
||||
version: 11.2.6
|
||||
resolution: "@nestjs/swagger@npm:11.2.6"
|
||||
version: 11.2.0
|
||||
resolution: "@nestjs/swagger@npm:11.2.0"
|
||||
dependencies:
|
||||
"@microsoft/tsdoc": "npm:0.16.0"
|
||||
"@microsoft/tsdoc": "npm:0.15.1"
|
||||
"@nestjs/mapped-types": "npm:2.1.0"
|
||||
js-yaml: "npm:4.1.1"
|
||||
lodash: "npm:4.17.23"
|
||||
path-to-regexp: "npm:8.3.0"
|
||||
swagger-ui-dist: "npm:5.31.0"
|
||||
js-yaml: "npm:4.1.0"
|
||||
lodash: "npm:4.17.21"
|
||||
path-to-regexp: "npm:8.2.0"
|
||||
swagger-ui-dist: "npm:5.21.0"
|
||||
peerDependencies:
|
||||
"@fastify/static": ^8.0.0 || ^9.0.0
|
||||
"@fastify/static": ^8.0.0
|
||||
"@nestjs/common": ^11.0.1
|
||||
"@nestjs/core": ^11.0.1
|
||||
class-transformer: "*"
|
||||
@@ -9039,7 +9023,7 @@ __metadata:
|
||||
optional: true
|
||||
class-validator:
|
||||
optional: true
|
||||
checksum: 10/818d249c50ed6df5e465d675b93945cfb4541c1c02fc2c442689cd19273b11991a1a2fc17d2b71a58242b8bc3e332628871a121d5bd955d306d396bab547ba35
|
||||
checksum: 10/2f8cee1f58ea5d887939d0d6f230f272a2526900057515d95253fb6d6c96dcd2d110396d70e9b74e6342cf83f4575b4dbe58ae97ba6252270b8318dcaa5fc8cd
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -9093,8 +9077,8 @@ __metadata:
|
||||
linkType: hard
|
||||
|
||||
"@nestjs/websockets@npm:^11.1.14":
|
||||
version: 11.1.17
|
||||
resolution: "@nestjs/websockets@npm:11.1.17"
|
||||
version: 11.1.14
|
||||
resolution: "@nestjs/websockets@npm:11.1.14"
|
||||
dependencies:
|
||||
iterare: "npm:1.2.1"
|
||||
object-hash: "npm:3.0.0"
|
||||
@@ -9108,7 +9092,7 @@ __metadata:
|
||||
peerDependenciesMeta:
|
||||
"@nestjs/platform-socket.io":
|
||||
optional: true
|
||||
checksum: 10/3d4b9db8c8c1a1172d3af54ed6e947749b52207875fdf9968367a08000dfd722867efa47c07bd2af587dbf568b222ceba98f3491cdbddbdb439a205e11d46cd1
|
||||
checksum: 10/06b67521692ceb7ef41ad6e1f8850469fe305a011838e0d3a466cfc9b35678e42a1783a329e0a16d1b9e45970a5afd9c5355c4924f7c2da67111e6d5b6f54c64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -23598,15 +23582,27 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"file-type@npm:21.3.2, file-type@npm:^21.0.0":
|
||||
version: 21.3.2
|
||||
resolution: "file-type@npm:21.3.2"
|
||||
"file-type@npm:21.3.0":
|
||||
version: 21.3.0
|
||||
resolution: "file-type@npm:21.3.0"
|
||||
dependencies:
|
||||
"@tokenizer/inflate": "npm:^0.4.1"
|
||||
strtok3: "npm:^10.3.4"
|
||||
token-types: "npm:^6.1.1"
|
||||
uint8array-extras: "npm:^1.4.0"
|
||||
checksum: 10/3912271811e0c745d43ff1f6c97e66d4b0d890c68d1041de4ef0c8068ede46f725ef3ed0f92c97d0cd2a261f84c3b51881d60ab797e47fa9a15e7ed227f04c85
|
||||
checksum: 10/8eb8707f34d0a0fd6c2d2b223edf1ed6235cb00a44a90216741be9e812ae08dc8497dbf4e2dd63e629728509cdf361070ed32ae2280724744463ab459da81a83
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"file-type@npm:^21.0.0":
|
||||
version: 21.3.1
|
||||
resolution: "file-type@npm:21.3.1"
|
||||
dependencies:
|
||||
"@tokenizer/inflate": "npm:^0.4.1"
|
||||
strtok3: "npm:^10.3.4"
|
||||
token-types: "npm:^6.1.1"
|
||||
uint8array-extras: "npm:^1.4.0"
|
||||
checksum: 10/0f99d4fa85184ea635cdccdfa677c7838bff790cdffde7fa9ec9f52e94fa8c0e7b6c2eeeb3f6a3d6dcc0a036192c13a8ec7008bdcef374e745ae0d64a162ad33
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -24693,22 +24689,22 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"graphql-ws@npm:6.0.7, graphql-ws@npm:^6.0.3":
|
||||
version: 6.0.7
|
||||
resolution: "graphql-ws@npm:6.0.7"
|
||||
"graphql-ws@npm:6.0.4, graphql-ws@npm:^6.0.3":
|
||||
version: 6.0.4
|
||||
resolution: "graphql-ws@npm:6.0.4"
|
||||
peerDependencies:
|
||||
"@fastify/websocket": ^10 || ^11
|
||||
crossws: ~0.3
|
||||
graphql: ^15.10.1 || ^16
|
||||
uWebSockets.js: ^20
|
||||
ws: ^8
|
||||
peerDependenciesMeta:
|
||||
"@fastify/websocket":
|
||||
optional: true
|
||||
crossws:
|
||||
uWebSockets.js:
|
||||
optional: true
|
||||
ws:
|
||||
optional: true
|
||||
checksum: 10/633b142a7a8683f900f1f3590a30ff696076d94d17cc8c0a42d069288cd8ca77b4967e87a9f7ac884c026106be42055b9e2bda5e7de28579bd2fc8e65d0b0424
|
||||
checksum: 10/9cfd6bdf03a65c2b390e049b0e6ce43dd3e428eb7df74afaad45bbfe1f074687a507509f5ab3f9467fea90a7fbe468558621e8c7be06729ea9dc56e116f236da
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -26405,14 +26401,14 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"js-yaml@npm:4.1.1, js-yaml@npm:^4.0.0, js-yaml@npm:^4.1.0, js-yaml@npm:^4.1.1":
|
||||
version: 4.1.1
|
||||
resolution: "js-yaml@npm:4.1.1"
|
||||
"js-yaml@npm:4.1.0":
|
||||
version: 4.1.0
|
||||
resolution: "js-yaml@npm:4.1.0"
|
||||
dependencies:
|
||||
argparse: "npm:^2.0.1"
|
||||
bin:
|
||||
js-yaml: bin/js-yaml.js
|
||||
checksum: 10/a52d0519f0f4ef5b4adc1cde466cb54c50d56e2b4a983b9d5c9c0f2f99462047007a6274d7e95617a21d3c91fde3ee6115536ed70991cd645ba8521058b78f77
|
||||
checksum: 10/c138a34a3fd0d08ebaf71273ad4465569a483b8a639e0b118ff65698d257c2791d3199e3f303631f2cb98213fa7b5f5d6a4621fd0fff819421b990d30d967140
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -26428,6 +26424,17 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"js-yaml@npm:^4.0.0, js-yaml@npm:^4.1.0, js-yaml@npm:^4.1.1":
|
||||
version: 4.1.1
|
||||
resolution: "js-yaml@npm:4.1.1"
|
||||
dependencies:
|
||||
argparse: "npm:^2.0.1"
|
||||
bin:
|
||||
js-yaml: bin/js-yaml.js
|
||||
checksum: 10/a52d0519f0f4ef5b4adc1cde466cb54c50d56e2b4a983b9d5c9c0f2f99462047007a6274d7e95617a21d3c91fde3ee6115536ed70991cd645ba8521058b78f77
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"jsbn@npm:1.1.0":
|
||||
version: 1.1.0
|
||||
resolution: "jsbn@npm:1.1.0"
|
||||
@@ -27332,7 +27339,14 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"lodash@npm:4.17.23, lodash@npm:^4.17.11, lodash@npm:^4.17.15, lodash@npm:^4.17.20, lodash@npm:^4.17.21, lodash@npm:^4.17.23, lodash@npm:^4.17.4, lodash@npm:~4.17.0":
|
||||
"lodash@npm:4.17.21":
|
||||
version: 4.17.21
|
||||
resolution: "lodash@npm:4.17.21"
|
||||
checksum: 10/c08619c038846ea6ac754abd6dd29d2568aa705feb69339e836dfa8d8b09abbb2f859371e86863eda41848221f9af43714491467b5b0299122431e202bb0c532
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"lodash@npm:^4.17.11, lodash@npm:^4.17.15, lodash@npm:^4.17.20, lodash@npm:^4.17.21, lodash@npm:^4.17.23, lodash@npm:^4.17.4, lodash@npm:~4.17.0":
|
||||
version: 4.17.23
|
||||
resolution: "lodash@npm:4.17.23"
|
||||
checksum: 10/82504c88250f58da7a5a4289f57a4f759c44946c005dd232821c7688b5fcfbf4a6268f6a6cdde4b792c91edd2f3b5398c1d2a0998274432cff76def48735e233
|
||||
@@ -28764,7 +28778,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"mkdirp@npm:^0.5.1":
|
||||
"mkdirp@npm:^0.5.1, mkdirp@npm:^0.5.6":
|
||||
version: 0.5.6
|
||||
resolution: "mkdirp@npm:0.5.6"
|
||||
dependencies:
|
||||
@@ -28931,7 +28945,22 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"multer@npm:2.1.1, multer@npm:^2.0.2":
|
||||
"multer@npm:2.0.2":
|
||||
version: 2.0.2
|
||||
resolution: "multer@npm:2.0.2"
|
||||
dependencies:
|
||||
append-field: "npm:^1.0.0"
|
||||
busboy: "npm:^1.6.0"
|
||||
concat-stream: "npm:^2.0.0"
|
||||
mkdirp: "npm:^0.5.6"
|
||||
object-assign: "npm:^4.1.1"
|
||||
type-is: "npm:^1.6.18"
|
||||
xtend: "npm:^4.0.2"
|
||||
checksum: 10/4bdcb07138cf72f93adc08a0dc27c058faab9f6721067a58f394fa546d73d11b7f100cdd66e733d649d184f9d1b402065f6888b31ec427409f056ee92c4367a6
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"multer@npm:^2.0.2":
|
||||
version: 2.1.1
|
||||
resolution: "multer@npm:2.1.1"
|
||||
dependencies:
|
||||
@@ -30336,6 +30365,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"path-to-regexp@npm:8.2.0":
|
||||
version: 8.2.0
|
||||
resolution: "path-to-regexp@npm:8.2.0"
|
||||
checksum: 10/23378276a172b8ba5f5fb824475d1818ca5ccee7bbdb4674701616470f23a14e536c1db11da9c9e6d82b82c556a817bbf4eee6e41b9ed20090ef9427cbb38e13
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"path-to-regexp@npm:8.3.0, path-to-regexp@npm:^8.0.0, path-to-regexp@npm:^8.3.0":
|
||||
version: 8.3.0
|
||||
resolution: "path-to-regexp@npm:8.3.0"
|
||||
@@ -34248,12 +34284,12 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"swagger-ui-dist@npm:5.31.0":
|
||||
version: 5.31.0
|
||||
resolution: "swagger-ui-dist@npm:5.31.0"
|
||||
"swagger-ui-dist@npm:5.21.0":
|
||||
version: 5.21.0
|
||||
resolution: "swagger-ui-dist@npm:5.21.0"
|
||||
dependencies:
|
||||
"@scarf/scarf": "npm:=1.4.0"
|
||||
checksum: 10/79a3fc72823c9f6340184a9702f86d5f251ebff41841d734627ad1c6f4ca3553fb888f2fa33cd911506101026fad386350095ee255e6916ebeba8fd5d8114894
|
||||
checksum: 10/5e34f4337b06d64ee6bbd64940cc63ace7c457deb98a1de2a387833c6d919543bfed1da662827191fd6c6c6762cc2f52b7ca3f316932a9bc3b12d51b718c5424
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -36365,9 +36401,9 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"ws@npm:8.19.0, ws@npm:^8.17.1, ws@npm:^8.18.0, ws@npm:^8.18.3":
|
||||
version: 8.19.0
|
||||
resolution: "ws@npm:8.19.0"
|
||||
"ws@npm:8.18.1":
|
||||
version: 8.18.1
|
||||
resolution: "ws@npm:8.18.1"
|
||||
peerDependencies:
|
||||
bufferutil: ^4.0.1
|
||||
utf-8-validate: ">=5.0.2"
|
||||
@@ -36376,7 +36412,7 @@ __metadata:
|
||||
optional: true
|
||||
utf-8-validate:
|
||||
optional: true
|
||||
checksum: 10/26e4901e93abaf73af9f26a93707c95b4845e91a7a347ec8c569e6e9be7f9df066f6c2b817b2d685544e208207898a750b78461e6e8d810c11a370771450c31b
|
||||
checksum: 10/3f38e9594f2af5b6324138e86b74df7d77bbb8e310bf8188679dd80bac0d1f47e51536a1923ac3365f31f3d8b25ea0b03e4ade466aa8292a86cd5defca64b19b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -36395,6 +36431,21 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"ws@npm:^8.17.1, ws@npm:^8.18.0, ws@npm:^8.18.3":
|
||||
version: 8.19.0
|
||||
resolution: "ws@npm:8.19.0"
|
||||
peerDependencies:
|
||||
bufferutil: ^4.0.1
|
||||
utf-8-validate: ">=5.0.2"
|
||||
peerDependenciesMeta:
|
||||
bufferutil:
|
||||
optional: true
|
||||
utf-8-validate:
|
||||
optional: true
|
||||
checksum: 10/26e4901e93abaf73af9f26a93707c95b4845e91a7a347ec8c569e6e9be7f9df066f6c2b817b2d685544e208207898a750b78461e6e8d810c11a370771450c31b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"ws@npm:~8.17.1":
|
||||
version: 8.17.1
|
||||
resolution: "ws@npm:8.17.1"
|
||||
@@ -36478,7 +36529,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"xtend@npm:^4.0.0":
|
||||
"xtend@npm:^4.0.0, xtend@npm:^4.0.2":
|
||||
version: 4.0.2
|
||||
resolution: "xtend@npm:4.0.2"
|
||||
checksum: 10/ac5dfa738b21f6e7f0dd6e65e1b3155036d68104e67e5d5d1bde74892e327d7e5636a076f625599dc394330a731861e87343ff184b0047fef1360a7ec0a5a36a
|
||||
|
||||
Reference in New Issue
Block a user