Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| fb2850d4fb | |||
| 87597b7701 | |||
| ae64b7764d | |||
| ed58fa228e | |||
| 8ceed77081 | |||
| d2ba54ec3a | |||
| 3416e2bdf1 | |||
| d952815932 | |||
| cde64ed80a | |||
| cfd88d0469 | |||
| 5e45fec296 |
@@ -57,7 +57,7 @@ Available methods: Helm chart, Nix package
|
||||
|
||||
In the works: Docker Compose, YunoHost
|
||||
|
||||
⚠️ For some advanced features (ex: Export as PDF) Docs relies on XL packages from BlockNote. These are licenced under AGPL-3.0 and are not MIT compatible. You can perfectly use Docs without these packages by setting the environment variable `PUBLISH_AS_MIT` to true. That way you'll build an image of the application without the features that are not MIT compatible. Read the [environment variables documentation](/docs/docs/env.md) for more information.
|
||||
⚠️ For some advanced features (ex: Export as PDF) Docs relies on XL packages from BlockNote. These are licenced under AGPL-3.0 and are not MIT compatible. You can perfectly use Docs without these packages by setting the environment variable `PUBLISH_AS_MIT` to true. That way you'll build an image of the application without the features that are not MIT compatible. Read the [environment variables documentation](/docs/env.md) for more information.
|
||||
|
||||
## Getting started 🔧
|
||||
|
||||
|
||||
+12
-12
@@ -26,9 +26,9 @@ readme = "README.md"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = [
|
||||
"beautifulsoup4==4.13.4",
|
||||
"boto3==1.38.18",
|
||||
"boto3==1.38.27",
|
||||
"Brotli==1.1.0",
|
||||
"celery[redis]==5.5.2",
|
||||
"celery[redis]==5.5.3",
|
||||
"django-configurations==2.5.1",
|
||||
"django-cors-headers==4.7.0",
|
||||
"django-countries==7.6.1",
|
||||
@@ -46,19 +46,19 @@ dependencies = [
|
||||
"easy_thumbnails==2.10",
|
||||
"factory_boy==3.3.3",
|
||||
"gunicorn==23.0.0",
|
||||
"jsonschema==4.23.0",
|
||||
"jsonschema==4.24.0",
|
||||
"lxml==5.4.0",
|
||||
"markdown==3.8",
|
||||
"mozilla-django-oidc==4.0.1",
|
||||
"nested-multipart-parser==1.5.0",
|
||||
"openai==1.79.0",
|
||||
"openai==1.82.1",
|
||||
"psycopg[binary]==3.2.9",
|
||||
"pycrdt==0.12.19",
|
||||
"pycrdt==0.12.20",
|
||||
"PyJWT==2.10.1",
|
||||
"python-magic==0.4.27",
|
||||
"redis<6.0.0",
|
||||
"requests==2.32.3",
|
||||
"sentry-sdk==2.28.0",
|
||||
"sentry-sdk==2.29.1",
|
||||
"whitenoise==6.9.0",
|
||||
]
|
||||
|
||||
@@ -72,10 +72,10 @@ dependencies = [
|
||||
dev = [
|
||||
"django-extensions==4.1",
|
||||
"django-test-migrations==1.5.0",
|
||||
"drf-spectacular-sidecar==2025.5.1",
|
||||
"freezegun==1.5.1",
|
||||
"drf-spectacular-sidecar==2025.6.1",
|
||||
"freezegun==1.5.2",
|
||||
"ipdb==0.13.13",
|
||||
"ipython==9.2.0",
|
||||
"ipython==9.3.0",
|
||||
"pyfakefs==5.8.0",
|
||||
"pylint-django==2.6.1",
|
||||
"pylint==3.3.7",
|
||||
@@ -83,10 +83,10 @@ dev = [
|
||||
"pytest-django==4.11.1",
|
||||
"pytest==8.3.5",
|
||||
"pytest-icdiff==0.9",
|
||||
"pytest-xdist==3.6.1",
|
||||
"pytest-xdist==3.7.0",
|
||||
"responses==0.25.7",
|
||||
"ruff==0.11.10",
|
||||
"types-requests==2.32.0.20250515",
|
||||
"ruff==0.11.12",
|
||||
"types-requests==2.32.0.20250602",
|
||||
]
|
||||
|
||||
[tool.setuptools]
|
||||
|
||||
@@ -16,56 +16,57 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@ag-media/react-pdf-table": "2.0.3",
|
||||
"@blocknote/code-block": "0.30.1",
|
||||
"@blocknote/core": "0.30.1",
|
||||
"@blocknote/mantine": "0.30.1",
|
||||
"@blocknote/react": "0.30.1",
|
||||
"@blocknote/xl-docx-exporter": "0.30.1",
|
||||
"@blocknote/xl-pdf-exporter": "0.30.1",
|
||||
"@blocknote/code-block": "0.31.1",
|
||||
"@blocknote/core": "0.31.1",
|
||||
"@blocknote/mantine": "0.31.1",
|
||||
"@blocknote/react": "0.31.1",
|
||||
"@blocknote/xl-docx-exporter": "0.31.1",
|
||||
"@blocknote/xl-pdf-exporter": "0.31.1",
|
||||
"@emoji-mart/data": "1.2.1",
|
||||
"@emoji-mart/react": "1.1.1",
|
||||
"@fontsource/material-icons": "5.2.5",
|
||||
"@gouvfr-lasuite/integration": "1.0.3",
|
||||
"@gouvfr-lasuite/ui-kit": "0.6.0",
|
||||
"@gouvfr-lasuite/ui-kit": "0.7.0",
|
||||
"@hocuspocus/provider": "2.15.2",
|
||||
"@openfun/cunningham-react": "3.1.0",
|
||||
"@react-pdf/renderer": "4.3.0",
|
||||
"@sentry/nextjs": "9.19.0",
|
||||
"@tanstack/react-query": "5.76.1",
|
||||
"@sentry/nextjs": "9.22.0",
|
||||
"@tanstack/react-query": "5.77.1",
|
||||
"canvg": "4.0.3",
|
||||
"clsx": "2.1.1",
|
||||
"cmdk": "1.1.1",
|
||||
"crisp-sdk-web": "1.0.25",
|
||||
"docx": "9.5.0",
|
||||
"emoji-mart": "5.6.0",
|
||||
"i18next": "25.1.3",
|
||||
"i18next": "25.2.1",
|
||||
"i18next-browser-languagedetector": "8.1.0",
|
||||
"idb": "8.0.3",
|
||||
"lodash": "4.17.21",
|
||||
"luxon": "3.6.1",
|
||||
"next": "15.3.2",
|
||||
"posthog-js": "1.242.2",
|
||||
"posthog-js": "1.246.0",
|
||||
"react": "*",
|
||||
"react-aria-components": "1.8.0",
|
||||
"react-aria-components": "1.9.0",
|
||||
"react-dom": "*",
|
||||
"react-i18next": "15.5.1",
|
||||
"react-i18next": "15.5.2",
|
||||
"react-intersection-observer": "9.16.0",
|
||||
"react-select": "5.10.1",
|
||||
"styled-components": "6.1.18",
|
||||
"tldraw": "3.13.1",
|
||||
"use-debounce": "10.0.4",
|
||||
"y-protocols": "1.0.6",
|
||||
"yjs": "*",
|
||||
"zustand": "5.0.4"
|
||||
"zustand": "5.0.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@svgr/webpack": "8.1.0",
|
||||
"@tanstack/react-query-devtools": "5.76.1",
|
||||
"@tanstack/react-query-devtools": "5.77.1",
|
||||
"@testing-library/dom": "10.4.0",
|
||||
"@testing-library/jest-dom": "6.6.3",
|
||||
"@testing-library/react": "16.3.0",
|
||||
"@testing-library/user-event": "14.6.1",
|
||||
"@types/jest": "29.5.14",
|
||||
"@types/lodash": "4.17.16",
|
||||
"@types/lodash": "4.17.17",
|
||||
"@types/luxon": "3.6.2",
|
||||
"@types/node": "*",
|
||||
"@types/react": "*",
|
||||
@@ -82,7 +83,7 @@
|
||||
"stylelint-config-standard": "38.0.0",
|
||||
"stylelint-prettier": "5.0.3",
|
||||
"typescript": "*",
|
||||
"webpack": "5.99.8",
|
||||
"webpack": "5.99.9",
|
||||
"workbox-webpack-plugin": "7.1.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ import { randomColor } from '../utils';
|
||||
import { BlockNoteSuggestionMenu } from './BlockNoteSuggestionMenu';
|
||||
import { BlockNoteToolbar } from './BlockNoteToolBar/BlockNoteToolbar';
|
||||
import { CalloutBlock, DividerBlock } from './custom-blocks';
|
||||
import { DrawBlock } from './custom-blocks/DrawBlock';
|
||||
|
||||
export const blockNoteSchema = withPageBreak(
|
||||
BlockNoteSchema.create({
|
||||
@@ -35,6 +36,7 @@ export const blockNoteSchema = withPageBreak(
|
||||
...defaultBlockSpecs,
|
||||
callout: CalloutBlock,
|
||||
divider: DividerBlock,
|
||||
draw: DrawBlock,
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
+2
@@ -15,6 +15,7 @@ import {
|
||||
getCalloutReactSlashMenuItems,
|
||||
getDividerReactSlashMenuItems,
|
||||
} from './custom-blocks';
|
||||
import { getDrawReactSlashMenuItems } from './custom-blocks/DrawBlock';
|
||||
|
||||
export const BlockNoteSuggestionMenu = () => {
|
||||
const editor = useBlockNoteEditor<DocsBlockSchema>();
|
||||
@@ -30,6 +31,7 @@ export const BlockNoteSuggestionMenu = () => {
|
||||
getPageBreakReactSlashMenuItems(editor),
|
||||
getCalloutReactSlashMenuItems(editor, t, basicBlocksName),
|
||||
getDividerReactSlashMenuItems(editor, t, basicBlocksName),
|
||||
getDrawReactSlashMenuItems(editor, t, basicBlocksName),
|
||||
),
|
||||
query,
|
||||
),
|
||||
|
||||
-23
@@ -16,7 +16,6 @@ import { useTranslation } from 'react-i18next';
|
||||
import { isAPIError } from '@/api';
|
||||
import { Box, Icon } from '@/components';
|
||||
import { useDocOptions, useDocStore } from '@/docs/doc-management/';
|
||||
import { useAnalytics } from '@/libs';
|
||||
|
||||
import {
|
||||
AITransformActions,
|
||||
@@ -218,44 +217,22 @@ const AIMenuItemTransform = ({
|
||||
children,
|
||||
icon,
|
||||
}: PropsWithChildren<AIMenuItemTransform>) => {
|
||||
const { trackEvent } = useAnalytics();
|
||||
const { mutateAsync: requestAI, isPending } = useDocAITransform();
|
||||
const editor = useBlockNoteEditor();
|
||||
|
||||
const requestAIAction = async (selectedBlocks: Block[]) => {
|
||||
const text = await editor.blocksToMarkdownLossy(selectedBlocks);
|
||||
|
||||
const requestStartTime = performance.now();
|
||||
const responseAI = await requestAI({
|
||||
text,
|
||||
action,
|
||||
docId,
|
||||
});
|
||||
const requestDuration = performance.now() - requestStartTime;
|
||||
|
||||
const eventProperties = {
|
||||
eventName: 'requestAIAction',
|
||||
action: action,
|
||||
docId: docId,
|
||||
requestLength: text.length,
|
||||
numberBlocks: selectedBlocks.length,
|
||||
requestDuration: requestDuration,
|
||||
};
|
||||
|
||||
if (!responseAI?.answer) {
|
||||
trackEvent({
|
||||
...eventProperties,
|
||||
status: 'error',
|
||||
});
|
||||
throw new Error('No response from AI');
|
||||
}
|
||||
|
||||
trackEvent({
|
||||
...eventProperties,
|
||||
status: 'success',
|
||||
responseLength: String(responseAI.answer),
|
||||
});
|
||||
|
||||
const markdown = await editor.tryParseMarkdownToBlocks(responseAI.answer);
|
||||
editor.replaceBlocks(selectedBlocks, markdown);
|
||||
};
|
||||
|
||||
+341
@@ -0,0 +1,341 @@
|
||||
/* eslint-disable react-hooks/rules-of-hooks */
|
||||
import {
|
||||
ReactRendererProps,
|
||||
defaultProps,
|
||||
insertOrUpdateBlock,
|
||||
} from '@blocknote/core';
|
||||
import { BlockTypeSelectItem, createReactBlockSpec } from '@blocknote/react';
|
||||
import { TFunction } from 'i18next';
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import {
|
||||
Editor,
|
||||
TLEventMapHandler,
|
||||
TLStore,
|
||||
Tldraw,
|
||||
getSnapshot,
|
||||
loadSnapshot,
|
||||
} from 'tldraw';
|
||||
import 'tldraw/tldraw.css';
|
||||
|
||||
import { Box, Icon } from '@/components';
|
||||
|
||||
import { DocsBlockNoteEditor } from '../../types';
|
||||
|
||||
import _ from 'lodash';
|
||||
|
||||
import { clear } from 'console';
|
||||
|
||||
/**
|
||||
* ----------------------------------------------------------------------------------
|
||||
* Collaborative **Draw** block – backed by a Y‑js document synced through
|
||||
* `@hocuspocus/provider` (see `useProviderStore`).
|
||||
* ----------------------------------------------------------------------------------
|
||||
*
|
||||
* Each Draw block owns its own Y‑Doc, identified by `roomId` (persisted in `propSchema`).
|
||||
* The block serialises a base‑64‑encoded Y‑js update (`drawingData`) so newcomers see
|
||||
* the latest snapshot _immediately_, without waiting for the websocket connection.
|
||||
*/
|
||||
export const DrawBlock = createReactBlockSpec(
|
||||
{
|
||||
type: 'draw',
|
||||
propSchema: {
|
||||
textAlignment: defaultProps.textAlignment,
|
||||
backgroundColor: defaultProps.backgroundColor,
|
||||
roomId: { default: `drawing-${Date.now()}` },
|
||||
drawingData: { default: '' },
|
||||
changeHistory: { default: '' },
|
||||
lastChange: { default: '' },
|
||||
increment: { default: 0 }, // Increment to force re-rendering
|
||||
},
|
||||
content: 'inline',
|
||||
},
|
||||
{
|
||||
render: ({ block, editor: editorBN }) => {
|
||||
const [editor, setEditor] = useState<Editor>();
|
||||
|
||||
const setAppToState = useCallback((editor: Editor) => {
|
||||
setEditor(editor);
|
||||
}, []);
|
||||
|
||||
const timeoutId = useRef<NodeJS.Timeout | null>(null);
|
||||
|
||||
const [storeEvents, setStoreEvents] = useState<string[]>([]);
|
||||
console.log('Loading saved drawing data');
|
||||
|
||||
useEffect(() => {
|
||||
if (block.props.drawingData && editor) {
|
||||
console.log('DDData');
|
||||
try {
|
||||
const drawingData = JSON.parse(block.props.drawingData);
|
||||
//const drawingData = block.props.drawingData;
|
||||
|
||||
// Update: Using the non-deprecated method to load the snapshot
|
||||
// Instead of editor.store.loadSnapshot(drawingData)
|
||||
loadSnapshot(editor.store, drawingData);
|
||||
|
||||
console.log('Successfully loaded drawing data');
|
||||
} catch (error) {
|
||||
console.error('Failed to load drawing data:', error);
|
||||
}
|
||||
}
|
||||
}, [block.props.drawingData, editor]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!editor) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Load saved drawing data if available
|
||||
|
||||
function logChangeEvent(eventInfo: string, changeData: any = null) {
|
||||
console.log(eventInfo);
|
||||
|
||||
// Get current properties
|
||||
// const currentProps = { ...block.props };
|
||||
|
||||
// // Get current changeHistory or initialize empty array
|
||||
// const currentHistory = Array.isArray(currentProps.changeHistory)
|
||||
// ? currentProps.changeHistory
|
||||
// : [];
|
||||
|
||||
// // Create a change record with timestamp
|
||||
// const changeRecord = {
|
||||
// timestamp: new Date().toISOString(),
|
||||
// event: eventInfo,
|
||||
// data: changeData,
|
||||
// };
|
||||
|
||||
// // Create new props object with all updated values
|
||||
// const updatedProps = {
|
||||
// ...currentProps,
|
||||
// changeHistory: [...currentHistory, changeRecord],
|
||||
// lastChange: changeRecord,
|
||||
// };
|
||||
|
||||
// // Add drawingData if available
|
||||
// if (changeData?.drawingData) {
|
||||
// updatedProps.drawingData = changeData.drawingData;
|
||||
// }
|
||||
|
||||
// Update the block with the new props
|
||||
// console.log('Updating block with props:', updatedProps);
|
||||
|
||||
// //editorBN.updateBlock(block, { props: updatedProps });
|
||||
|
||||
// if (timeoutId.current) {
|
||||
// clearTimeout(timeoutId.current);
|
||||
// }
|
||||
// timeoutId.current = setTimeout(() => {
|
||||
// editorBN.updateBlock(block, {
|
||||
// props: updatedProps,
|
||||
// });
|
||||
// }, 300);
|
||||
if (!editor) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (timeoutId.current) {
|
||||
clearTimeout(timeoutId.current);
|
||||
}
|
||||
timeoutId.current = setTimeout(() => {
|
||||
const snapshot = getSnapshot(editor.store);
|
||||
//const snapshot = JSON.stringify(editor.store.serialize());
|
||||
|
||||
console.log('Captured drawing snapshot:', snapshot);
|
||||
|
||||
// Only update drawingData property to avoid multiple updates
|
||||
const currentProps = { ...block.props };
|
||||
|
||||
editorBN.updateBlock(block, {
|
||||
props: {
|
||||
drawingData: JSON.stringify(snapshot),
|
||||
increment: currentProps.increment + 1, // Increment to force re-rendering
|
||||
},
|
||||
});
|
||||
}, 300);
|
||||
|
||||
//setStoreEvents((events) => [...events, eventInfo]);
|
||||
}
|
||||
|
||||
//[1]
|
||||
const handleChangeEvent: TLEventMapHandler<'change'> = (change) => {
|
||||
// Added
|
||||
for (const record of Object.values(change.changes.added)) {
|
||||
if (record.typeName === 'shape') {
|
||||
logChangeEvent(`created shape (${record.type})`, {
|
||||
action: 'created',
|
||||
shapeType: record.type,
|
||||
shape: record,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Updated
|
||||
for (const [from, to] of Object.values(change.changes.updated)) {
|
||||
if (
|
||||
from.typeName === 'instance' &&
|
||||
to.typeName === 'instance' &&
|
||||
from.currentPageId !== to.currentPageId
|
||||
) {
|
||||
logChangeEvent(
|
||||
`changed page (${from.currentPageId}, ${to.currentPageId})`,
|
||||
{
|
||||
action: 'changedPage',
|
||||
fromPageId: from.currentPageId,
|
||||
toPageId: to.currentPageId,
|
||||
},
|
||||
);
|
||||
} else if (
|
||||
from.id.startsWith('shape') &&
|
||||
to.id.startsWith('shape')
|
||||
) {
|
||||
let diff = _.reduce(
|
||||
from,
|
||||
(result: any[], value, key: string) =>
|
||||
_.isEqual(value, to[key])
|
||||
? result
|
||||
: result.concat([key, to[key]]),
|
||||
[],
|
||||
);
|
||||
const diffObj = {};
|
||||
|
||||
if (diff?.[0] === 'props') {
|
||||
diff = _.reduce(
|
||||
from.props,
|
||||
(result: any[], value, key) =>
|
||||
_.isEqual(value, to.props[key])
|
||||
? result
|
||||
: result.concat([key, to.props[key]]),
|
||||
[],
|
||||
);
|
||||
|
||||
// Convert diff array to object for better storage
|
||||
for (let i = 0; i < diff.length; i += 2) {
|
||||
diffObj[diff[i]] = diff[i + 1];
|
||||
}
|
||||
}
|
||||
|
||||
logChangeEvent(`updated shape (${JSON.stringify(diff)})`, {
|
||||
action: 'updated',
|
||||
shapeId: from.id,
|
||||
changes: diffObj,
|
||||
from: from,
|
||||
to: to,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Removed
|
||||
for (const record of Object.values(change.changes.removed)) {
|
||||
if (record.typeName === 'shape') {
|
||||
logChangeEvent(`deleted shape (${record.type})`, {
|
||||
action: 'deleted',
|
||||
shapeType: record.type,
|
||||
shape: record,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Store the entire drawing state periodically when changes occur
|
||||
// if (
|
||||
// Object.keys(change.changes.added).length > 0 ||
|
||||
// Object.keys(change.changes.updated).length > 0 ||
|
||||
// Object.keys(change.changes.removed).length > 0
|
||||
// ) {
|
||||
// // Capture the current drawing state if available
|
||||
// if (editor.store) {
|
||||
// try {
|
||||
// // Update: Using the non-deprecated method to get the snapshot
|
||||
// // Instead of
|
||||
// //
|
||||
// const snapshot = getSnapshot(editor.store);
|
||||
// //const snapshot = JSON.stringify(editor.store.serialize());
|
||||
|
||||
// console.log('Captured drawing snapshot:', snapshot);
|
||||
|
||||
// // Only update drawingData property to avoid multiple updates
|
||||
// const currentProps = { ...block.props };
|
||||
|
||||
// if (timeoutId.current) {
|
||||
// clearTimeout(timeoutId.current);
|
||||
// }
|
||||
// timeoutId.current = setTimeout(() => {
|
||||
// editorBN.updateBlock(block, {
|
||||
// props: {
|
||||
// ...currentProps,
|
||||
// drawingData: snapshot,
|
||||
// },
|
||||
// });
|
||||
// }, 300);
|
||||
|
||||
// console.log('Drawing snapshot updated');
|
||||
// } catch (error) {
|
||||
// console.error('Failed to capture drawing snapshot:', error);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
};
|
||||
|
||||
// [2]
|
||||
const cleanupFunction = editor.store.listen(handleChangeEvent, {
|
||||
source: 'user',
|
||||
scope: 'all',
|
||||
});
|
||||
|
||||
return () => {
|
||||
cleanupFunction();
|
||||
};
|
||||
}, [block, editor, editorBN]);
|
||||
|
||||
return (
|
||||
<Box style={{ width: '100%', height: 300 }}>
|
||||
{/*
|
||||
* We deliberately pass the TL‑store directly. TLDraw will observe the
|
||||
* changes – including those coming over the wire – and re‑render.
|
||||
*/}
|
||||
<Tldraw onMount={setAppToState} />
|
||||
</Box>
|
||||
);
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
/**
|
||||
* Slash‑menu helper → inserts a new collaborative draw block with a unique `roomId`.
|
||||
*/
|
||||
export const getDrawReactSlashMenuItems = (
|
||||
editor: DocsBlockNoteEditor,
|
||||
t: TFunction<'translation', undefined>,
|
||||
group: string,
|
||||
) => [
|
||||
{
|
||||
title: t('Draw'),
|
||||
onItemClick: () => {
|
||||
insertOrUpdateBlock(editor, {
|
||||
type: 'draw',
|
||||
props: {
|
||||
roomId: `drawing-${Date.now()}`,
|
||||
drawingData: null,
|
||||
changeHistory: [],
|
||||
lastChange: null,
|
||||
},
|
||||
});
|
||||
},
|
||||
aliases: ['draw'],
|
||||
group,
|
||||
icon: <Icon iconName="draw" $size="18px" />,
|
||||
subtext: t('Add a collaborative canvas'),
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* Formatting‑toolbar item so users can transform an existing block into a Draw block.
|
||||
*/
|
||||
export const getDrawFormattingToolbarItems = (
|
||||
t: TFunction<'translation', undefined>,
|
||||
): BlockTypeSelectItem => ({
|
||||
name: t('Draw'),
|
||||
type: 'draw',
|
||||
icon: () => <Icon iconName="lightbulb" $size="16px" />,
|
||||
isSelected: (block) => block.type === 'draw',
|
||||
});
|
||||
@@ -8,14 +8,12 @@ import { useCreateDoc } from '@/docs/doc-management';
|
||||
import { DocSearchModal } from '@/docs/doc-search';
|
||||
import { useAuth } from '@/features/auth';
|
||||
import { useCmdK } from '@/hook/useCmdK';
|
||||
import { useAnalytics } from '@/libs';
|
||||
|
||||
import { useLeftPanelStore } from '../stores';
|
||||
|
||||
export const LeftPanelHeader = ({ children }: PropsWithChildren) => {
|
||||
const router = useRouter();
|
||||
const { authenticated } = useAuth();
|
||||
const { trackEvent } = useAnalytics();
|
||||
const [isSearchModalOpen, setIsSearchModalOpen] = useState(false);
|
||||
|
||||
const openSearchModal = useCallback(() => {
|
||||
@@ -25,10 +23,8 @@ export const LeftPanelHeader = ({ children }: PropsWithChildren) => {
|
||||
return;
|
||||
}
|
||||
|
||||
trackEvent({ eventName: 'openSearchModal', position: 'LeftPanelHeader' });
|
||||
|
||||
setIsSearchModalOpen(true);
|
||||
}, [trackEvent]);
|
||||
}, []);
|
||||
|
||||
const closeSearchModal = useCallback(() => {
|
||||
setIsSearchModalOpen(false);
|
||||
@@ -50,7 +46,6 @@ export const LeftPanelHeader = ({ children }: PropsWithChildren) => {
|
||||
};
|
||||
|
||||
const createNewDoc = () => {
|
||||
trackEvent({ eventName: 'createNewDoc', position: 'LeftPanelHeader' });
|
||||
createDoc();
|
||||
};
|
||||
|
||||
|
||||
@@ -9,15 +9,7 @@ type AnalyticEventUser = {
|
||||
email: string;
|
||||
};
|
||||
|
||||
export type AnalyticEventGeneric = {
|
||||
eventName: string;
|
||||
[key: string]: string | number;
|
||||
};
|
||||
|
||||
export type AnalyticEvent =
|
||||
| AnalyticEventClick
|
||||
| AnalyticEventUser
|
||||
| AnalyticEventGeneric;
|
||||
export type AnalyticEvent = AnalyticEventClick | AnalyticEventUser;
|
||||
|
||||
export abstract class AbstractAnalytic {
|
||||
public constructor() {
|
||||
|
||||
@@ -3,7 +3,7 @@ import posthog from 'posthog-js';
|
||||
import { PostHogProvider as PHProvider } from 'posthog-js/react';
|
||||
import { JSX, PropsWithChildren, ReactNode, useEffect } from 'react';
|
||||
|
||||
import { AbstractAnalytic, AnalyticEvent, AnalyticEventGeneric } from '@/libs/';
|
||||
import { AbstractAnalytic, AnalyticEvent } from '@/libs/';
|
||||
|
||||
export class PostHogAnalytic extends AbstractAnalytic {
|
||||
private conf?: PostHogConf = undefined;
|
||||
@@ -19,14 +19,8 @@ export class PostHogAnalytic extends AbstractAnalytic {
|
||||
}
|
||||
|
||||
public trackEvent(evt: AnalyticEvent): void {
|
||||
switch (evt.eventName) {
|
||||
case 'user':
|
||||
posthog.identify(evt.id as string, { email: evt.email });
|
||||
break;
|
||||
default:
|
||||
const { eventName, ...properties } = evt as AnalyticEventGeneric;
|
||||
posthog.capture(eventName, properties);
|
||||
break;
|
||||
if (evt.eventName === 'user') {
|
||||
posthog.identify(evt.id, { email: evt.email });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -28,8 +28,8 @@
|
||||
"server:test": "yarn COLLABORATION_SERVER run test"
|
||||
},
|
||||
"resolutions": {
|
||||
"@types/node": "22.15.19",
|
||||
"@types/react": "19.1.4",
|
||||
"@types/node": "22.15.21",
|
||||
"@types/react": "19.1.5",
|
||||
"@types/react-dom": "19.1.5",
|
||||
"@typescript-eslint/eslint-plugin": "8.32.1",
|
||||
"@typescript-eslint/parser": "8.32.1",
|
||||
|
||||
@@ -16,10 +16,10 @@
|
||||
"node": ">=18"
|
||||
},
|
||||
"dependencies": {
|
||||
"@blocknote/server-util": "0.30.1",
|
||||
"@blocknote/server-util": "0.31.1",
|
||||
"@hocuspocus/server": "2.15.2",
|
||||
"@sentry/node": "9.19.0",
|
||||
"@sentry/profiling-node": "9.19.0",
|
||||
"@sentry/node": "9.22.0",
|
||||
"@sentry/profiling-node": "9.22.0",
|
||||
"axios": "1.9.0",
|
||||
"cors": "2.8.5",
|
||||
"express": "5.1.0",
|
||||
|
||||
+2401
-209
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user