✨(frontend) add an EmojiPicker in the document tree
This allows users to easily add emojis easily to their documents from the tree, enhancing the overall user experience.
This commit is contained in:
committed by
Anthony LC
parent
294922f966
commit
b667200ebd
@@ -9,7 +9,11 @@ import {
|
||||
verifyDocName,
|
||||
} from './utils-common';
|
||||
import { addNewMember } from './utils-share';
|
||||
import { clickOnAddRootSubPage, createRootSubPage } from './utils-sub-pages';
|
||||
import {
|
||||
clickOnAddRootSubPage,
|
||||
createRootSubPage,
|
||||
getTreeRow,
|
||||
} from './utils-sub-pages';
|
||||
|
||||
test.describe('Doc Tree', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
@@ -298,6 +302,36 @@ test.describe('Doc Tree', () => {
|
||||
// Now test keyboard navigation on sub-document
|
||||
await expect(docTree.getByText(docChild)).toBeVisible();
|
||||
});
|
||||
|
||||
test('it updates the child icon from the tree', async ({
|
||||
page,
|
||||
browserName,
|
||||
}) => {
|
||||
const [docParent] = await createDoc(
|
||||
page,
|
||||
'doc-child-emoji',
|
||||
browserName,
|
||||
1,
|
||||
);
|
||||
await verifyDocName(page, docParent);
|
||||
|
||||
const { name: docChild } = await createRootSubPage(
|
||||
page,
|
||||
browserName,
|
||||
'doc-child-emoji-child',
|
||||
);
|
||||
|
||||
// Update the emoji from the tree
|
||||
const row = await getTreeRow(page, docChild);
|
||||
await row.locator('.--docs--doc-icon').click();
|
||||
await page.getByRole('button', { name: '😀' }).first().click();
|
||||
|
||||
// Verify the emoji is updated in the tree and in the document title
|
||||
await expect(row.getByText('😀')).toBeVisible();
|
||||
await expect(
|
||||
page.getByRole('textbox', { name: 'Document title' }),
|
||||
).toContainText('😀');
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Doc Tree: Inheritance', () => {
|
||||
|
||||
@@ -19,7 +19,7 @@ export const EmojiPicker = ({
|
||||
const { i18n } = useTranslation();
|
||||
|
||||
return (
|
||||
<Box $position="absolute" $zIndex={1000} $margin="2rem 0 0 0">
|
||||
<Box>
|
||||
<Picker
|
||||
data={emojiData}
|
||||
locale={i18n.resolvedLanguage}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export * from './AccessibleImageBlock';
|
||||
export * from './CalloutBlock';
|
||||
export { default as emojidata } from './initEmojiCallout';
|
||||
export * from './PdfBlock';
|
||||
export * from './UploadLoaderBlock';
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
export * from './DocEditor';
|
||||
export * from './EmojiPicker';
|
||||
export * from './custom-blocks/';
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { useTreeContext } from '@gouvfr-lasuite/ui-kit';
|
||||
import { Tooltip } from '@openfun/cunningham-react';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@@ -8,14 +7,12 @@ import { Box, Text } from '@/components';
|
||||
import { useCunninghamTheme } from '@/cunningham';
|
||||
import {
|
||||
Doc,
|
||||
KEY_DOC,
|
||||
KEY_LIST_DOC,
|
||||
useDocStore,
|
||||
useDocTitleUpdate,
|
||||
useIsCollaborativeEditable,
|
||||
useTrans,
|
||||
useUpdateDoc,
|
||||
} from '@/docs/doc-management';
|
||||
import { useBroadcastStore, useResponsiveStore } from '@/stores';
|
||||
import { useResponsiveStore } from '@/stores';
|
||||
|
||||
interface DocTitleProps {
|
||||
doc: Doc;
|
||||
@@ -54,47 +51,17 @@ const DocTitleInput = ({ doc }: DocTitleProps) => {
|
||||
const { t } = useTranslation();
|
||||
const { colorsTokens } = useCunninghamTheme();
|
||||
const [titleDisplay, setTitleDisplay] = useState(doc.title);
|
||||
const treeContext = useTreeContext<Doc>();
|
||||
|
||||
const { untitledDocument } = useTrans();
|
||||
|
||||
const { broadcast } = useBroadcastStore();
|
||||
|
||||
const { mutate: updateDoc } = useUpdateDoc({
|
||||
listInvalideQueries: [KEY_DOC, KEY_LIST_DOC],
|
||||
onSuccess(updatedDoc) {
|
||||
// Broadcast to every user connected to the document
|
||||
broadcast(`${KEY_DOC}-${updatedDoc.id}`);
|
||||
|
||||
if (!treeContext) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (treeContext.root?.id === updatedDoc.id) {
|
||||
treeContext?.setRoot(updatedDoc);
|
||||
} else {
|
||||
treeContext?.treeData.updateNode(updatedDoc.id, updatedDoc);
|
||||
}
|
||||
},
|
||||
});
|
||||
const { updateDocTitle } = useDocTitleUpdate();
|
||||
|
||||
const handleTitleSubmit = useCallback(
|
||||
(inputText: string) => {
|
||||
let sanitizedTitle = inputText.trim();
|
||||
sanitizedTitle = sanitizedTitle.replace(/(\r\n|\n|\r)/gm, '');
|
||||
|
||||
// When blank we set to untitled
|
||||
if (!sanitizedTitle) {
|
||||
setTitleDisplay('');
|
||||
}
|
||||
|
||||
// If mutation we update
|
||||
if (sanitizedTitle !== doc.title) {
|
||||
setTitleDisplay(sanitizedTitle);
|
||||
updateDoc({ id: doc.id, title: sanitizedTitle });
|
||||
}
|
||||
const sanitizedTitle = updateDocTitle(doc, inputText.trim());
|
||||
setTitleDisplay(sanitizedTitle);
|
||||
},
|
||||
[doc.id, doc.title, updateDoc],
|
||||
[doc, updateDocTitle],
|
||||
);
|
||||
|
||||
const handleKeyDown = (e: React.KeyboardEvent) => {
|
||||
|
||||
@@ -1,8 +1,18 @@
|
||||
import { Text, TextType } from '@/components';
|
||||
import { MouseEvent, useRef, useState } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
|
||||
import { BoxButton, Icon, TextType } from '@/components';
|
||||
import { EmojiPicker, emojidata } from '@/docs/doc-editor/';
|
||||
|
||||
import { useDocTitleUpdate } from '../hooks/useDocTitleUpdate';
|
||||
|
||||
type DocIconProps = TextType & {
|
||||
emoji?: string | null;
|
||||
defaultIcon: React.ReactNode;
|
||||
docId?: string;
|
||||
title?: string;
|
||||
onEmojiUpdate?: (emoji: string) => void;
|
||||
withEmojiPicker?: boolean;
|
||||
};
|
||||
|
||||
export const DocIcon = ({
|
||||
@@ -11,22 +21,101 @@ export const DocIcon = ({
|
||||
$size = 'sm',
|
||||
$variation = '1000',
|
||||
$weight = '400',
|
||||
docId,
|
||||
title,
|
||||
onEmojiUpdate,
|
||||
withEmojiPicker = false,
|
||||
...textProps
|
||||
}: DocIconProps) => {
|
||||
if (!emoji) {
|
||||
return <>{defaultIcon}</>;
|
||||
const { updateDocEmoji } = useDocTitleUpdate();
|
||||
|
||||
const iconRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const [openEmojiPicker, setOpenEmojiPicker] = useState<boolean>(false);
|
||||
const [pickerPosition, setPickerPosition] = useState<{
|
||||
top: number;
|
||||
left: number;
|
||||
}>({ top: 0, left: 0 });
|
||||
|
||||
if (!withEmojiPicker && !emoji) {
|
||||
return defaultIcon;
|
||||
}
|
||||
|
||||
const toggleEmojiPicker = (e: MouseEvent) => {
|
||||
if (withEmojiPicker) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
|
||||
if (!openEmojiPicker && iconRef.current) {
|
||||
const rect = iconRef.current.getBoundingClientRect();
|
||||
setPickerPosition({
|
||||
top: rect.bottom + window.scrollY + 8,
|
||||
left: rect.left + window.scrollX,
|
||||
});
|
||||
}
|
||||
|
||||
setOpenEmojiPicker(!openEmojiPicker);
|
||||
}
|
||||
};
|
||||
|
||||
const handleEmojiSelect = ({ native }: { native: string }) => {
|
||||
setOpenEmojiPicker(false);
|
||||
|
||||
// Update document emoji if docId is provided
|
||||
if (docId && title !== undefined) {
|
||||
updateDocEmoji(docId, title ?? '', native);
|
||||
}
|
||||
|
||||
// Call the optional callback
|
||||
onEmojiUpdate?.(native);
|
||||
};
|
||||
|
||||
const handleClickOutside = () => {
|
||||
setOpenEmojiPicker(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<Text
|
||||
{...textProps}
|
||||
$size={$size}
|
||||
$variation={$variation}
|
||||
$weight={$weight}
|
||||
aria-hidden="true"
|
||||
data-testid="doc-emoji-icon"
|
||||
>
|
||||
{emoji}
|
||||
</Text>
|
||||
<>
|
||||
<BoxButton
|
||||
ref={iconRef}
|
||||
onClick={toggleEmojiPicker}
|
||||
$position="relative"
|
||||
className="--docs--doc-icon"
|
||||
>
|
||||
{!emoji ? (
|
||||
defaultIcon
|
||||
) : (
|
||||
<Icon
|
||||
{...textProps}
|
||||
iconName={emoji}
|
||||
$size={$size}
|
||||
$variation={$variation}
|
||||
$weight={$weight}
|
||||
aria-hidden="true"
|
||||
data-testid="doc-emoji-icon"
|
||||
>
|
||||
{emoji}
|
||||
</Icon>
|
||||
)}
|
||||
</BoxButton>
|
||||
{openEmojiPicker &&
|
||||
createPortal(
|
||||
<div
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: pickerPosition.top,
|
||||
left: pickerPosition.left,
|
||||
zIndex: 1000,
|
||||
}}
|
||||
>
|
||||
<EmojiPicker
|
||||
emojiData={emojidata}
|
||||
onEmojiSelect={handleEmojiSelect}
|
||||
onClickOutside={handleClickOutside}
|
||||
/>
|
||||
</div>,
|
||||
document.body,
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from './DocIcon';
|
||||
export * from './DocPage403';
|
||||
export * from './ModalRemoveDoc';
|
||||
export * from './SimpleDocItem';
|
||||
|
||||
@@ -0,0 +1,274 @@
|
||||
import { renderHook, waitFor } from '@testing-library/react';
|
||||
import fetchMock from 'fetch-mock';
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { AppWrapper } from '@/tests/utils';
|
||||
|
||||
import { Doc } from '../../types';
|
||||
import { useDocTitleUpdate } from '../useDocTitleUpdate';
|
||||
|
||||
// Mock useBroadcastStore
|
||||
vi.mock('@/stores', () => ({
|
||||
useBroadcastStore: () => ({
|
||||
broadcast: vi.fn(),
|
||||
}),
|
||||
}));
|
||||
|
||||
describe('useDocTitleUpdate', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
fetchMock.restore();
|
||||
});
|
||||
|
||||
it('should return the correct functions and state', () => {
|
||||
const { result } = renderHook(() => useDocTitleUpdate(), {
|
||||
wrapper: AppWrapper,
|
||||
});
|
||||
|
||||
expect(result.current.updateDocTitle).toBeDefined();
|
||||
expect(result.current.updateDocEmoji).toBeDefined();
|
||||
expect(typeof result.current.updateDocTitle).toBe('function');
|
||||
expect(typeof result.current.updateDocEmoji).toBe('function');
|
||||
});
|
||||
|
||||
describe('updateDocTitle', () => {
|
||||
it('should call updateDoc with sanitized title', async () => {
|
||||
fetchMock.patch('http://test.jest/api/v1.0/documents/test-doc-id/', {
|
||||
body: JSON.stringify({
|
||||
id: 'test-doc-id',
|
||||
title: 'My Document',
|
||||
}),
|
||||
});
|
||||
|
||||
const { result } = renderHook(() => useDocTitleUpdate(), {
|
||||
wrapper: AppWrapper,
|
||||
});
|
||||
|
||||
const sanitizedTitle = result.current.updateDocTitle(
|
||||
{ id: 'test-doc-id', title: '' } as Doc,
|
||||
' My Document \n\r',
|
||||
);
|
||||
|
||||
expect(sanitizedTitle).toBe('My Document');
|
||||
|
||||
await waitFor(() => {
|
||||
expect(fetchMock.calls().length).toBe(1);
|
||||
});
|
||||
expect(fetchMock.calls()[0][0]).toBe(
|
||||
'http://test.jest/api/v1.0/documents/test-doc-id/',
|
||||
);
|
||||
expect(fetchMock.calls()[0][1]).toEqual({
|
||||
method: 'PATCH',
|
||||
credentials: 'include',
|
||||
body: JSON.stringify({ title: 'My Document' }),
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle empty title and not call updateDoc', async () => {
|
||||
fetchMock.patch('http://test.jest/api/v1.0/documents/test-doc-id/', {
|
||||
body: JSON.stringify({
|
||||
id: 'test-doc-id',
|
||||
title: 'My Document',
|
||||
}),
|
||||
});
|
||||
|
||||
const { result } = renderHook(() => useDocTitleUpdate(), {
|
||||
wrapper: AppWrapper,
|
||||
});
|
||||
|
||||
const sanitizedTitle = result.current.updateDocTitle(
|
||||
{ id: 'test-doc-id', title: '' } as Doc,
|
||||
'',
|
||||
);
|
||||
|
||||
expect(sanitizedTitle).toBe('');
|
||||
|
||||
await waitFor(() => {
|
||||
expect(fetchMock.calls().length).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
it('should remove newlines and carriage returns', async () => {
|
||||
fetchMock.patch('http://test.jest/api/v1.0/documents/test-doc-id/', {
|
||||
body: JSON.stringify({
|
||||
id: 'test-doc-id',
|
||||
title: 'My Document',
|
||||
}),
|
||||
});
|
||||
|
||||
const { result } = renderHook(() => useDocTitleUpdate(), {
|
||||
wrapper: AppWrapper,
|
||||
});
|
||||
|
||||
const sanitizedTitle = result.current.updateDocTitle(
|
||||
{ id: 'test-doc-id', title: '' } as Doc,
|
||||
'Title\nwith\r\nnewlines',
|
||||
);
|
||||
|
||||
expect(sanitizedTitle).toBe('Titlewithnewlines');
|
||||
|
||||
await waitFor(() => {
|
||||
expect(fetchMock.calls().length).toBe(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateDocEmoji', () => {
|
||||
it('should call updateDoc with emoji and title without existing emoji', async () => {
|
||||
fetchMock.patch('http://test.jest/api/v1.0/documents/test-doc-id/', {
|
||||
body: JSON.stringify({
|
||||
id: 'test-doc-id',
|
||||
title: 'My Document',
|
||||
}),
|
||||
});
|
||||
|
||||
const { result } = renderHook(() => useDocTitleUpdate(), {
|
||||
wrapper: AppWrapper,
|
||||
});
|
||||
|
||||
result.current.updateDocEmoji('test-doc-id', 'My Document', '🚀');
|
||||
|
||||
await waitFor(() => {
|
||||
expect(fetchMock.calls().length).toBe(1);
|
||||
});
|
||||
expect(fetchMock.calls()[0][0]).toBe(
|
||||
'http://test.jest/api/v1.0/documents/test-doc-id/',
|
||||
);
|
||||
expect(fetchMock.calls()[0][1]).toEqual({
|
||||
method: 'PATCH',
|
||||
credentials: 'include',
|
||||
body: JSON.stringify({ title: '🚀 My Document' }),
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
});
|
||||
});
|
||||
|
||||
it('should replace existing emoji with new one', async () => {
|
||||
fetchMock.patch('http://test.jest/api/v1.0/documents/test-doc-id/', {
|
||||
body: JSON.stringify({
|
||||
id: 'test-doc-id',
|
||||
title: 'My Document',
|
||||
}),
|
||||
});
|
||||
|
||||
const { result } = renderHook(() => useDocTitleUpdate(), {
|
||||
wrapper: AppWrapper,
|
||||
});
|
||||
|
||||
result.current.updateDocEmoji('test-doc-id', '📝 My Document', '🚀');
|
||||
|
||||
await waitFor(() => {
|
||||
expect(fetchMock.calls().length).toBe(1);
|
||||
});
|
||||
expect(fetchMock.calls()[0][1]).toEqual({
|
||||
method: 'PATCH',
|
||||
credentials: 'include',
|
||||
body: JSON.stringify({ title: '🚀 My Document' }),
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle title with only emoji', async () => {
|
||||
fetchMock.patch('http://test.jest/api/v1.0/documents/test-doc-id/', {
|
||||
body: JSON.stringify({
|
||||
id: 'test-doc-id',
|
||||
title: 'My Document',
|
||||
}),
|
||||
});
|
||||
|
||||
const { result } = renderHook(() => useDocTitleUpdate(), {
|
||||
wrapper: AppWrapper,
|
||||
});
|
||||
|
||||
result.current.updateDocEmoji('test-doc-id', '📝', '🚀');
|
||||
|
||||
await waitFor(() => {
|
||||
expect(fetchMock.calls().length).toBe(1);
|
||||
});
|
||||
expect(fetchMock.calls()[0][1]).toEqual({
|
||||
method: 'PATCH',
|
||||
credentials: 'include',
|
||||
body: JSON.stringify({ title: '🚀 ' }),
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle empty title', async () => {
|
||||
fetchMock.patch('http://test.jest/api/v1.0/documents/test-doc-id/', {
|
||||
body: JSON.stringify({
|
||||
id: 'test-doc-id',
|
||||
title: 'My Document',
|
||||
}),
|
||||
});
|
||||
|
||||
const { result } = renderHook(() => useDocTitleUpdate(), {
|
||||
wrapper: AppWrapper,
|
||||
});
|
||||
|
||||
result.current.updateDocEmoji('test-doc-id', '', '🚀');
|
||||
|
||||
await waitFor(() => {
|
||||
expect(fetchMock.calls().length).toBe(1);
|
||||
});
|
||||
expect(fetchMock.calls()[0][1]).toEqual({
|
||||
method: 'PATCH',
|
||||
credentials: 'include',
|
||||
body: JSON.stringify({ title: '🚀 ' }),
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('onSuccess callback', () => {
|
||||
it('should call onSuccess when provided', async () => {
|
||||
fetchMock.patch('http://test.jest/api/v1.0/documents/test-doc-id/', {
|
||||
body: JSON.stringify({
|
||||
id: 'test-doc-id',
|
||||
title: 'Updated Document',
|
||||
}),
|
||||
});
|
||||
|
||||
const onSuccess = vi.fn();
|
||||
const { result } = renderHook(() => useDocTitleUpdate({ onSuccess }), {
|
||||
wrapper: AppWrapper,
|
||||
});
|
||||
|
||||
result.current.updateDocTitle(
|
||||
{ id: 'test-doc-id', title: 'Old Document' } as Doc,
|
||||
'Updated Document',
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(fetchMock.calls().length).toBe(1);
|
||||
});
|
||||
|
||||
expect(onSuccess).toHaveBeenCalledWith({
|
||||
id: 'test-doc-id',
|
||||
title: 'Updated Document',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('onError callback', () => {
|
||||
it('should call onError when provided', async () => {
|
||||
fetchMock.patch('http://test.jest/api/v1.0/documents/test-doc-id/', {
|
||||
throws: new Error('Update failed'),
|
||||
});
|
||||
|
||||
const onError = vi.fn();
|
||||
const { result } = renderHook(() => useDocTitleUpdate({ onError }), {
|
||||
wrapper: AppWrapper,
|
||||
});
|
||||
|
||||
try {
|
||||
result.current.updateDocTitle(
|
||||
{ id: 'test-doc-id', title: 'Old Document' } as Doc,
|
||||
'Updated Document',
|
||||
);
|
||||
} catch {
|
||||
expect(fetchMock.calls().length).toBe(1);
|
||||
expect(onError).toHaveBeenCalledWith(new Error('Update failed'));
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,6 +1,7 @@
|
||||
export * from './useCollaboration';
|
||||
export * from './useCopyDocLink';
|
||||
export * from './useCreateChildDocTree';
|
||||
export * from './useDocTitleUpdate';
|
||||
export * from './useDocUtils';
|
||||
export * from './useIsCollaborativeEditable';
|
||||
export * from './useTrans';
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
import { useTreeContext } from '@gouvfr-lasuite/ui-kit';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import {
|
||||
Doc,
|
||||
KEY_DOC,
|
||||
KEY_LIST_DOC,
|
||||
getEmojiAndTitle,
|
||||
useUpdateDoc,
|
||||
} from '@/docs/doc-management';
|
||||
import { useBroadcastStore } from '@/stores';
|
||||
|
||||
interface UseDocUpdateOptions {
|
||||
onSuccess?: (updatedDoc: Doc) => void;
|
||||
onError?: (error: Error) => void;
|
||||
}
|
||||
|
||||
export const useDocTitleUpdate = (options?: UseDocUpdateOptions) => {
|
||||
const { broadcast } = useBroadcastStore();
|
||||
const treeContext = useTreeContext<Doc>();
|
||||
|
||||
const { mutate: updateDoc, ...mutationResult } = useUpdateDoc({
|
||||
listInvalideQueries: [KEY_DOC, KEY_LIST_DOC],
|
||||
onSuccess: (updatedDoc) => {
|
||||
// Broadcast to every user connected to the document
|
||||
broadcast(`${KEY_DOC}-${updatedDoc.id}`);
|
||||
|
||||
if (treeContext) {
|
||||
if (treeContext.root?.id === updatedDoc.id) {
|
||||
treeContext?.setRoot(updatedDoc);
|
||||
} else {
|
||||
treeContext?.treeData.updateNode(updatedDoc.id, updatedDoc);
|
||||
}
|
||||
}
|
||||
|
||||
options?.onSuccess?.(updatedDoc);
|
||||
},
|
||||
onError: (error) => {
|
||||
options?.onError?.(error);
|
||||
},
|
||||
});
|
||||
|
||||
const updateDocTitle = useCallback(
|
||||
(doc: Doc, title: string) => {
|
||||
const sanitizedTitle = title.trim().replace(/(\r\n|\n|\r)/gm, '');
|
||||
|
||||
// When blank we set to untitled
|
||||
if (!sanitizedTitle) {
|
||||
updateDoc({ id: doc.id, title: '' });
|
||||
return '';
|
||||
}
|
||||
|
||||
// If mutation we update
|
||||
if (sanitizedTitle !== doc.title) {
|
||||
updateDoc({ id: doc.id, title: sanitizedTitle });
|
||||
}
|
||||
|
||||
return sanitizedTitle;
|
||||
},
|
||||
[updateDoc],
|
||||
);
|
||||
|
||||
const updateDocEmoji = useCallback(
|
||||
(docId: string, title: string, emoji: string) => {
|
||||
const { titleWithoutEmoji } = getEmojiAndTitle(title);
|
||||
updateDoc({ id: docId, title: `${emoji} ${titleWithoutEmoji}` });
|
||||
},
|
||||
[updateDoc],
|
||||
);
|
||||
|
||||
return {
|
||||
...mutationResult,
|
||||
updateDocTitle,
|
||||
updateDocEmoji,
|
||||
};
|
||||
};
|
||||
@@ -12,10 +12,10 @@ import { Box, BoxButton, Icon, Text } from '@/components';
|
||||
import { useCunninghamTheme } from '@/cunningham';
|
||||
import {
|
||||
Doc,
|
||||
DocIcon,
|
||||
getEmojiAndTitle,
|
||||
useTrans,
|
||||
} from '@/features/docs/doc-management';
|
||||
import { DocIcon } from '@/features/docs/doc-management/components/DocIcon';
|
||||
import { useLeftPanelStore } from '@/features/left-panel';
|
||||
import { useResponsiveStore } from '@/stores';
|
||||
|
||||
@@ -166,11 +166,14 @@ export const DocSubPageItem = (props: TreeViewNodeProps<Doc>) => {
|
||||
min-width: 0;
|
||||
`}
|
||||
>
|
||||
<Box $width="16px" $height="16px">
|
||||
<Box>
|
||||
<DocIcon
|
||||
emoji={emoji}
|
||||
withEmojiPicker={doc.abilities.partial_update}
|
||||
defaultIcon={<SubPageIcon color={colorsTokens['primary-400']} />}
|
||||
$size="sm"
|
||||
docId={doc.id}
|
||||
title={doc.title}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
|
||||
@@ -78,11 +78,11 @@ export const useBroadcastStore = create<BroadcastState>((set, get) => ({
|
||||
}));
|
||||
},
|
||||
broadcast: (taskLabel) => {
|
||||
const { task } = get().tasks[taskLabel];
|
||||
if (!task) {
|
||||
const obTask = get().tasks?.[taskLabel];
|
||||
if (!obTask || !obTask.task) {
|
||||
console.warn(`Task ${taskLabel} is not defined`);
|
||||
return;
|
||||
}
|
||||
task.push([`broadcast: ${taskLabel}`]);
|
||||
obTask.task.push([`broadcast: ${taskLabel}`]);
|
||||
},
|
||||
}));
|
||||
|
||||
Reference in New Issue
Block a user