mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-16 17:46:18 +08:00
fix interfere in Menu.tsx
This commit is contained in:
@@ -3,8 +3,12 @@ import { services } from '@toeverything/datasource/db-service';
|
||||
import type { ReturnEditorBlock } from '@toeverything/datasource/db-service';
|
||||
import type { TDShape } from '@toeverything/components/board-types';
|
||||
import { Editor } from '@toeverything/components/board-shapes';
|
||||
import { usePageClientWidth } from '@toeverything/datasource/state';
|
||||
|
||||
export const useShapes = (workspace: string, rootBlockId: string) => {
|
||||
const { pageClientWidth } = usePageClientWidth();
|
||||
// page padding left and right total 300px
|
||||
const editorShapeInitSize = pageClientWidth - 300;
|
||||
const [blocks, setBlocks] = useState<ReturnEditorBlock[]>();
|
||||
useEffect(() => {
|
||||
services.api.editorBlock
|
||||
@@ -58,8 +62,9 @@ export const useShapes = (workspace: string, rootBlockId: string) => {
|
||||
acc[block.id] = { ...shapeProps, id: block.id };
|
||||
} else {
|
||||
acc[block.id] = Editor.getShape({
|
||||
point: [groupCount * 740, 200],
|
||||
point: [groupCount * editorShapeInitSize + 200, 200],
|
||||
id: block.id,
|
||||
size: [editorShapeInitSize, 200],
|
||||
...shapeProps,
|
||||
affineId: shapeProps.affineId ?? block.id,
|
||||
workspace: block.workspace,
|
||||
|
||||
@@ -64,18 +64,7 @@ export class EditorUtil extends TDShapeUtil<T, E> {
|
||||
};
|
||||
|
||||
Component = TDShapeUtil.Component<T, E, TDMeta>(
|
||||
(
|
||||
{
|
||||
shape,
|
||||
meta: {
|
||||
app: { useStore },
|
||||
},
|
||||
events,
|
||||
isEditing,
|
||||
onShapeChange,
|
||||
},
|
||||
ref
|
||||
) => {
|
||||
({ shape, meta: { app }, events, isEditing, onShapeChange }, ref) => {
|
||||
const containerRef = useRef<HTMLDivElement>();
|
||||
const {
|
||||
workspace,
|
||||
@@ -83,7 +72,7 @@ export class EditorUtil extends TDShapeUtil<T, E> {
|
||||
size: [width, height],
|
||||
} = shape;
|
||||
|
||||
const state = useStore();
|
||||
const state = app.useStore();
|
||||
const { currentPageId } = state.appState;
|
||||
const { editingId } = state.document.pageStates[currentPageId];
|
||||
const { shapes } = state.document.pages[currentPageId];
|
||||
@@ -134,11 +123,19 @@ export class EditorUtil extends TDShapeUtil<T, E> {
|
||||
[isEditing]
|
||||
);
|
||||
|
||||
const activateIfEditing = useCallback(() => {
|
||||
if (editingText && editingId !== shape.id) {
|
||||
app.setEditingText(shape.id);
|
||||
}
|
||||
}, [app, shape.id, editingText, editingId]);
|
||||
|
||||
return (
|
||||
<HTMLContainer ref={ref} {...events}>
|
||||
<Container
|
||||
ref={containerRef}
|
||||
onPointerDown={stopPropagation}
|
||||
onMouseEnter={activateIfEditing}
|
||||
onDragEnter={activateIfEditing}
|
||||
>
|
||||
<MemoAffineEditor
|
||||
workspace={workspace}
|
||||
|
||||
@@ -967,6 +967,29 @@ export class TldrawApp extends StateManager<TDSnapshot> {
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* used for EditorUtil only
|
||||
* @param id
|
||||
* @returns
|
||||
*/
|
||||
setEditingText = (id: string) => {
|
||||
if (this.readOnly) return;
|
||||
|
||||
this.patchState(
|
||||
{
|
||||
document: {
|
||||
pageStates: {
|
||||
[this.currentPageId]: {
|
||||
selectedIds: [id],
|
||||
editingId: id,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
`set_editing_id`
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Set or clear the hovered id
|
||||
* @param id [string]
|
||||
|
||||
@@ -149,6 +149,7 @@ export interface TDMeta {
|
||||
isDarkMode: boolean;
|
||||
app: {
|
||||
useStore: () => TDSnapshot;
|
||||
setEditingText: (id: string) => void;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
+16
-15
@@ -26,7 +26,6 @@ export const PendantHistoryPanel = ({
|
||||
const { getProperties } = useRecastBlockMeta();
|
||||
const { getProperty } = useRecastBlockMeta();
|
||||
|
||||
const { getAllValue } = getRecastItemValue(block);
|
||||
const recastBlock = useRecastBlock();
|
||||
|
||||
const [history, setHistory] = useState<RecastBlockValue[]>([]);
|
||||
@@ -34,7 +33,7 @@ export const PendantHistoryPanel = ({
|
||||
|
||||
useEffect(() => {
|
||||
const init = async () => {
|
||||
const currentBlockValues = getAllValue();
|
||||
const currentBlockValues = getRecastItemValue(block).getAllValue();
|
||||
const allProperties = getProperties();
|
||||
const missProperties = allProperties.filter(
|
||||
property => !currentBlockValues.find(v => v.id === property.id)
|
||||
@@ -52,24 +51,26 @@ export const PendantHistoryPanel = ({
|
||||
return history;
|
||||
}, {});
|
||||
|
||||
const blockHistory = await Promise.all(
|
||||
Object.entries(historyMap).map(
|
||||
async ([propertyId, blockId]) => {
|
||||
const latestValueBlock = (
|
||||
await groupBlock.children()
|
||||
).find((block: AsyncBlock) => block.id === blockId);
|
||||
const blockHistory = (
|
||||
await Promise.all(
|
||||
Object.entries(historyMap).map(
|
||||
async ([propertyId, blockId]) => {
|
||||
const latestValueBlock = (
|
||||
await groupBlock.children()
|
||||
).find((block: AsyncBlock) => block.id === blockId);
|
||||
|
||||
return getRecastItemValue(latestValueBlock).getValue(
|
||||
propertyId as RecastPropertyId
|
||||
);
|
||||
}
|
||||
return getRecastItemValue(
|
||||
latestValueBlock
|
||||
).getValue(propertyId as RecastPropertyId);
|
||||
}
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
).filter(v => v);
|
||||
setHistory(blockHistory);
|
||||
};
|
||||
|
||||
init();
|
||||
}, [getAllValue, getProperties, groupBlock, recastBlock]);
|
||||
}, [block, getProperties, groupBlock, recastBlock]);
|
||||
|
||||
return (
|
||||
<StyledPendantHistoryPanel>
|
||||
|
||||
+12
-109
@@ -1,11 +1,11 @@
|
||||
import React, { CSSProperties, useState, useEffect } from 'react';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { nanoid } from 'nanoid';
|
||||
import { Input, Option, Select, Tooltip } from '@toeverything/components/ui';
|
||||
import { HelpCenterIcon } from '@toeverything/components/icons';
|
||||
import { AsyncBlock } from '../../editor';
|
||||
|
||||
import { IconMap, pendantOptions } from '../config';
|
||||
import { OptionType, PendantOptions, PendantTypes } from '../types';
|
||||
import { PendantOptions } from '../types';
|
||||
import { PendantModifyPanel } from '../pendant-modify-panel';
|
||||
import {
|
||||
StyledDivider,
|
||||
@@ -15,23 +15,13 @@ import {
|
||||
StyledPopoverSubTitle,
|
||||
StyledPopoverWrapper,
|
||||
} from '../StyledComponent';
|
||||
import {
|
||||
genSelectOptionId,
|
||||
InformationProperty,
|
||||
useRecastBlock,
|
||||
useRecastBlockMeta,
|
||||
useSelectProperty,
|
||||
} from '../../recast-block';
|
||||
import {
|
||||
genInitialOptions,
|
||||
getOfficialSelected,
|
||||
getPendantConfigByType,
|
||||
} from '../utils';
|
||||
import { usePendant } from '../use-pendant';
|
||||
import { genInitialOptions, getPendantConfigByType } from '../utils';
|
||||
import { useOnCreateSure } from './hooks';
|
||||
|
||||
const upperFirst = (str: string) => {
|
||||
return `${str[0].toUpperCase()}${str.slice(1)}`;
|
||||
};
|
||||
|
||||
export const CreatePendantPanel = ({
|
||||
block,
|
||||
onSure,
|
||||
@@ -41,9 +31,7 @@ export const CreatePendantPanel = ({
|
||||
}) => {
|
||||
const [selectedOption, setSelectedOption] = useState<PendantOptions>();
|
||||
const [fieldName, setFieldName] = useState<string>('');
|
||||
const { addProperty, removeProperty } = useRecastBlockMeta();
|
||||
const { createSelect } = useSelectProperty();
|
||||
const { setPendant } = usePendant(block);
|
||||
const onCreateSure = useOnCreateSure({ block });
|
||||
|
||||
useEffect(() => {
|
||||
selectedOption &&
|
||||
@@ -110,91 +98,13 @@ export const CreatePendantPanel = ({
|
||||
getPendantConfigByType(selectedOption.type)
|
||||
)}
|
||||
iconConfig={getPendantConfigByType(selectedOption.type)}
|
||||
// isStatusSelect={selectedOption.name === 'Status'}
|
||||
onSure={async (type, newPropertyItem, newValue) => {
|
||||
if (!fieldName) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
type === PendantTypes.MultiSelect ||
|
||||
type === PendantTypes.Select ||
|
||||
type === PendantTypes.Status
|
||||
) {
|
||||
const newProperty = await createSelect({
|
||||
name: fieldName,
|
||||
options: newPropertyItem,
|
||||
type,
|
||||
});
|
||||
|
||||
const selectedId = getOfficialSelected({
|
||||
isMulti: type === PendantTypes.MultiSelect,
|
||||
options: newProperty.options,
|
||||
tempOptions: newPropertyItem,
|
||||
tempSelectedId: newValue,
|
||||
});
|
||||
|
||||
await setPendant(newProperty, selectedId);
|
||||
} else if (type === PendantTypes.Information) {
|
||||
const emailOptions = genOptionWithId(
|
||||
newPropertyItem.emailOptions
|
||||
);
|
||||
|
||||
const phoneOptions = genOptionWithId(
|
||||
newPropertyItem.phoneOptions
|
||||
);
|
||||
|
||||
const locationOptions = genOptionWithId(
|
||||
newPropertyItem.locationOptions
|
||||
);
|
||||
|
||||
const newProperty = await addProperty({
|
||||
type,
|
||||
name: fieldName,
|
||||
emailOptions,
|
||||
phoneOptions,
|
||||
locationOptions,
|
||||
} as Omit<InformationProperty, 'id'>);
|
||||
|
||||
await setPendant(newProperty, {
|
||||
email: getOfficialSelected({
|
||||
isMulti: true,
|
||||
options: emailOptions,
|
||||
tempOptions:
|
||||
newPropertyItem.emailOptions,
|
||||
tempSelectedId: newValue.email,
|
||||
}),
|
||||
phone: getOfficialSelected({
|
||||
isMulti: true,
|
||||
options: phoneOptions,
|
||||
tempOptions:
|
||||
newPropertyItem.phoneOptions,
|
||||
tempSelectedId: newValue.phone,
|
||||
}),
|
||||
location: getOfficialSelected({
|
||||
isMulti: true,
|
||||
options: locationOptions,
|
||||
tempOptions:
|
||||
newPropertyItem.locationOptions,
|
||||
tempSelectedId: newValue.location,
|
||||
}),
|
||||
});
|
||||
} else {
|
||||
// TODO: Color and background should use pendant config, but ui is not design now
|
||||
const iconConfig = getPendantConfigByType(type);
|
||||
// TODO: Color and background should be choose by user in the future
|
||||
const newProperty = await addProperty({
|
||||
type: type,
|
||||
name: fieldName,
|
||||
background:
|
||||
iconConfig.background as CSSProperties['background'],
|
||||
color: iconConfig.color as CSSProperties['color'],
|
||||
iconName: iconConfig.iconName,
|
||||
});
|
||||
|
||||
await setPendant(newProperty, newValue);
|
||||
}
|
||||
|
||||
await onCreateSure({
|
||||
type,
|
||||
newPropertyItem,
|
||||
newValue,
|
||||
fieldName,
|
||||
});
|
||||
onSure?.();
|
||||
}}
|
||||
/>
|
||||
@@ -203,10 +113,3 @@ export const CreatePendantPanel = ({
|
||||
</StyledPopoverWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
const genOptionWithId = (options: OptionType[] = []) => {
|
||||
return options.map((option: OptionType) => ({
|
||||
...option,
|
||||
id: genSelectOptionId(),
|
||||
}));
|
||||
};
|
||||
|
||||
+15
-131
@@ -1,27 +1,16 @@
|
||||
import { useState } from 'react';
|
||||
import { Input, Tooltip } from '@toeverything/components/ui';
|
||||
import { HelpCenterIcon } from '@toeverything/components/icons';
|
||||
import { PendantModifyPanel } from '../pendant-modify-panel';
|
||||
import type { AsyncBlock } from '../../editor';
|
||||
import {
|
||||
genSelectOptionId,
|
||||
InformationProperty,
|
||||
type MultiSelectProperty,
|
||||
type RecastBlockValue,
|
||||
type RecastMetaProperty,
|
||||
type SelectOption,
|
||||
type SelectProperty,
|
||||
useRecastBlockMeta,
|
||||
useSelectProperty,
|
||||
} from '../../recast-block';
|
||||
import { OptionType, PendantTypes, TempInformationType } from '../types';
|
||||
import {
|
||||
getOfficialSelected,
|
||||
getPendantConfigByType,
|
||||
// getPendantIconsConfigByNameOrType,
|
||||
} from '../utils';
|
||||
import { getPendantConfigByType } from '../utils';
|
||||
import { usePendant } from '../use-pendant';
|
||||
import {
|
||||
StyledPopoverWrapper,
|
||||
StyledOperationTitle,
|
||||
StyledOperationLabel,
|
||||
StyledInputEndAdornment,
|
||||
StyledDivider,
|
||||
@@ -29,10 +18,8 @@ import {
|
||||
StyledPopoverSubTitle,
|
||||
} from '../StyledComponent';
|
||||
import { IconMap, pendantOptions } from '../config';
|
||||
import { Input, Tooltip } from '@toeverything/components/ui';
|
||||
import { HelpCenterIcon } from '@toeverything/components/icons';
|
||||
|
||||
type SelectPropertyType = MultiSelectProperty | SelectProperty;
|
||||
import { useOnUpdateSure } from './hooks';
|
||||
|
||||
type Props = {
|
||||
value: RecastBlockValue;
|
||||
@@ -53,13 +40,12 @@ export const UpdatePendantPanel = ({
|
||||
onCancel,
|
||||
titleEditable = false,
|
||||
}: Props) => {
|
||||
const { updateSelect } = useSelectProperty();
|
||||
const { setPendant, removePendant } = usePendant(block);
|
||||
const pendantOption = pendantOptions.find(v => v.type === property.type);
|
||||
const iconConfig = getPendantConfigByType(property.type);
|
||||
const { removePendant } = usePendant(block);
|
||||
const Icon = IconMap[iconConfig.iconName];
|
||||
const { updateProperty } = useRecastBlockMeta();
|
||||
const [fieldTitle, setFieldTitle] = useState(property.name);
|
||||
const [fieldName, setFieldName] = useState(property.name);
|
||||
const onUpdateSure = useOnUpdateSure({ block, property });
|
||||
|
||||
return (
|
||||
<StyledPopoverWrapper>
|
||||
@@ -77,10 +63,10 @@ export const UpdatePendantPanel = ({
|
||||
<StyledOperationLabel>Field Title</StyledOperationLabel>
|
||||
{titleEditable ? (
|
||||
<Input
|
||||
value={fieldTitle}
|
||||
value={fieldName}
|
||||
placeholder="Input your field name here"
|
||||
onChange={e => {
|
||||
setFieldTitle(e.target.value);
|
||||
setFieldName(e.target.value);
|
||||
}}
|
||||
endAdornment={
|
||||
<Tooltip content="Help info here">
|
||||
@@ -111,114 +97,12 @@ export const UpdatePendantPanel = ({
|
||||
property={property}
|
||||
type={property.type}
|
||||
onSure={async (type, newPropertyItem, newValue) => {
|
||||
if (
|
||||
type === PendantTypes.MultiSelect ||
|
||||
type === PendantTypes.Select ||
|
||||
type === PendantTypes.Status
|
||||
) {
|
||||
const newOptions = newPropertyItem as OptionType[];
|
||||
let selectProperty = property as SelectPropertyType;
|
||||
const deleteOptionIds = selectProperty.options
|
||||
.filter(o => {
|
||||
return !newOptions.find(no => no.id === o.id);
|
||||
})
|
||||
.map(o => o.id);
|
||||
const addOptions = newOptions.filter(
|
||||
o => typeof o.id === 'number'
|
||||
);
|
||||
|
||||
const { addSelectOptions, removeSelectOptions } =
|
||||
updateSelect(selectProperty);
|
||||
|
||||
deleteOptionIds.length &&
|
||||
(selectProperty = (await removeSelectOptions(
|
||||
...deleteOptionIds
|
||||
)) as SelectPropertyType);
|
||||
|
||||
addOptions.length &&
|
||||
(selectProperty = (await addSelectOptions(
|
||||
...(addOptions as unknown as Omit<
|
||||
SelectOption,
|
||||
'id'
|
||||
>[])
|
||||
)) as SelectPropertyType);
|
||||
|
||||
const selectedId = getOfficialSelected({
|
||||
isMulti: type === PendantTypes.MultiSelect,
|
||||
options: selectProperty.options,
|
||||
tempOptions: newPropertyItem,
|
||||
tempSelectedId: newValue,
|
||||
});
|
||||
|
||||
await setPendant(selectProperty, selectedId);
|
||||
} else if (type === PendantTypes.Information) {
|
||||
// const { emailOptions, phoneOptions, locationOptions } =
|
||||
// property as InformationProperty;
|
||||
const optionGroup =
|
||||
newPropertyItem as TempInformationType;
|
||||
|
||||
const emailOptions = optionGroup.emailOptions.map(
|
||||
option => {
|
||||
if (typeof option.id === 'number') {
|
||||
option.id = genSelectOptionId();
|
||||
}
|
||||
return option;
|
||||
}
|
||||
);
|
||||
const phoneOptions = optionGroup.phoneOptions.map(
|
||||
option => {
|
||||
if (typeof option.id === 'number') {
|
||||
option.id = genSelectOptionId();
|
||||
}
|
||||
return option;
|
||||
}
|
||||
);
|
||||
const locationOptions = optionGroup.locationOptions.map(
|
||||
option => {
|
||||
if (typeof option.id === 'number') {
|
||||
option.id = genSelectOptionId();
|
||||
}
|
||||
return option;
|
||||
}
|
||||
);
|
||||
|
||||
const newProperty = await updateProperty({
|
||||
...property,
|
||||
emailOptions,
|
||||
phoneOptions,
|
||||
locationOptions,
|
||||
} as InformationProperty);
|
||||
|
||||
await setPendant(newProperty, {
|
||||
email: getOfficialSelected({
|
||||
isMulti: true,
|
||||
options: emailOptions as SelectOption[],
|
||||
tempOptions: newPropertyItem.emailOptions,
|
||||
tempSelectedId: newValue.email,
|
||||
}),
|
||||
phone: getOfficialSelected({
|
||||
isMulti: true,
|
||||
options: phoneOptions as SelectOption[],
|
||||
tempOptions: newPropertyItem.phoneOptions,
|
||||
tempSelectedId: newValue.phone,
|
||||
}),
|
||||
location: getOfficialSelected({
|
||||
isMulti: true,
|
||||
options: locationOptions as SelectOption[],
|
||||
tempOptions: newPropertyItem.locationOptions,
|
||||
tempSelectedId: newValue.location,
|
||||
}),
|
||||
});
|
||||
} else {
|
||||
await setPendant(property, newValue);
|
||||
}
|
||||
|
||||
if (fieldTitle !== property.name) {
|
||||
await updateProperty({
|
||||
...property,
|
||||
name: fieldTitle,
|
||||
});
|
||||
}
|
||||
await onUpdateSure({
|
||||
type,
|
||||
newPropertyItem,
|
||||
newValue,
|
||||
fieldName,
|
||||
});
|
||||
onSure?.();
|
||||
}}
|
||||
onDelete={
|
||||
|
||||
@@ -0,0 +1,265 @@
|
||||
import type { CSSProperties } from 'react';
|
||||
import {
|
||||
genSelectOptionId,
|
||||
type InformationProperty,
|
||||
type MultiSelectProperty,
|
||||
type RecastMetaProperty,
|
||||
type SelectOption,
|
||||
type SelectProperty,
|
||||
useRecastBlockMeta,
|
||||
useSelectProperty,
|
||||
} from '../../recast-block';
|
||||
import { type AsyncBlock } from '../../editor';
|
||||
import { usePendant } from '../use-pendant';
|
||||
import {
|
||||
type OptionType,
|
||||
PendantTypes,
|
||||
type TempInformationType,
|
||||
} from '../types';
|
||||
import {
|
||||
checkPendantForm,
|
||||
getOfficialSelected,
|
||||
getPendantConfigByType,
|
||||
} from '../utils';
|
||||
import { message } from '@toeverything/components/ui';
|
||||
|
||||
type SelectPropertyType = MultiSelectProperty | SelectProperty;
|
||||
type SureParams = {
|
||||
fieldName: string;
|
||||
type: PendantTypes;
|
||||
newPropertyItem: any;
|
||||
newValue: any;
|
||||
};
|
||||
|
||||
const genOptionWithId = (options: OptionType[] = []) => {
|
||||
return options.map((option: OptionType) => ({
|
||||
...option,
|
||||
id: genSelectOptionId(),
|
||||
}));
|
||||
};
|
||||
// Callback function for pendant create
|
||||
export const useOnCreateSure = ({ block }: { block: AsyncBlock }) => {
|
||||
const { addProperty } = useRecastBlockMeta();
|
||||
const { createSelect } = useSelectProperty();
|
||||
const { setPendant } = usePendant(block);
|
||||
|
||||
return async ({
|
||||
type,
|
||||
fieldName,
|
||||
newPropertyItem,
|
||||
newValue,
|
||||
}: SureParams) => {
|
||||
const checkResult = checkPendantForm(
|
||||
type,
|
||||
fieldName,
|
||||
newPropertyItem,
|
||||
newValue
|
||||
);
|
||||
|
||||
if (!checkResult.passed) {
|
||||
await message.error(checkResult.message);
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
type === PendantTypes.MultiSelect ||
|
||||
type === PendantTypes.Select ||
|
||||
type === PendantTypes.Status
|
||||
) {
|
||||
const newProperty = await createSelect({
|
||||
name: fieldName,
|
||||
options: newPropertyItem,
|
||||
type,
|
||||
});
|
||||
|
||||
const selectedId = getOfficialSelected({
|
||||
isMulti: type === PendantTypes.MultiSelect,
|
||||
options: newProperty.options,
|
||||
tempOptions: newPropertyItem,
|
||||
tempSelectedId: newValue,
|
||||
});
|
||||
|
||||
await setPendant(newProperty, selectedId);
|
||||
} else if (type === PendantTypes.Information) {
|
||||
const emailOptions = genOptionWithId(newPropertyItem.emailOptions);
|
||||
|
||||
const phoneOptions = genOptionWithId(newPropertyItem.phoneOptions);
|
||||
|
||||
const locationOptions = genOptionWithId(
|
||||
newPropertyItem.locationOptions
|
||||
);
|
||||
|
||||
const newProperty = await addProperty({
|
||||
type,
|
||||
name: fieldName,
|
||||
emailOptions,
|
||||
phoneOptions,
|
||||
locationOptions,
|
||||
} as Omit<InformationProperty, 'id'>);
|
||||
|
||||
await setPendant(newProperty, {
|
||||
email: getOfficialSelected({
|
||||
isMulti: true,
|
||||
options: emailOptions,
|
||||
tempOptions: newPropertyItem.emailOptions,
|
||||
tempSelectedId: newValue.email,
|
||||
}),
|
||||
phone: getOfficialSelected({
|
||||
isMulti: true,
|
||||
options: phoneOptions,
|
||||
tempOptions: newPropertyItem.phoneOptions,
|
||||
tempSelectedId: newValue.phone,
|
||||
}),
|
||||
location: getOfficialSelected({
|
||||
isMulti: true,
|
||||
options: locationOptions,
|
||||
tempOptions: newPropertyItem.locationOptions,
|
||||
tempSelectedId: newValue.location,
|
||||
}),
|
||||
});
|
||||
} else {
|
||||
// TODO: Color and background should use pendant config, but ui is not design now
|
||||
const iconConfig = getPendantConfigByType(type);
|
||||
// TODO: Color and background should be choose by user in the future
|
||||
const newProperty = await addProperty({
|
||||
type: type,
|
||||
name: fieldName,
|
||||
background:
|
||||
iconConfig.background as CSSProperties['background'],
|
||||
color: iconConfig.color as CSSProperties['color'],
|
||||
iconName: iconConfig.iconName,
|
||||
});
|
||||
|
||||
await setPendant(newProperty, newValue);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// Callback function for pendant update
|
||||
export const useOnUpdateSure = ({
|
||||
block,
|
||||
property,
|
||||
}: {
|
||||
block: AsyncBlock;
|
||||
property: RecastMetaProperty;
|
||||
}) => {
|
||||
const { updateSelect } = useSelectProperty();
|
||||
const { setPendant } = usePendant(block);
|
||||
const { updateProperty } = useRecastBlockMeta();
|
||||
|
||||
return async ({
|
||||
type,
|
||||
fieldName,
|
||||
newPropertyItem,
|
||||
newValue,
|
||||
}: SureParams) => {
|
||||
const checkResult = checkPendantForm(
|
||||
type,
|
||||
fieldName,
|
||||
newPropertyItem,
|
||||
newValue
|
||||
);
|
||||
|
||||
if (!checkResult.passed) {
|
||||
await message.error(checkResult.message);
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
type === PendantTypes.MultiSelect ||
|
||||
type === PendantTypes.Select ||
|
||||
type === PendantTypes.Status
|
||||
) {
|
||||
const newOptions = newPropertyItem as OptionType[];
|
||||
let selectProperty = property as SelectPropertyType;
|
||||
const deleteOptionIds = selectProperty.options
|
||||
.filter(o => {
|
||||
return !newOptions.find(no => no.id === o.id);
|
||||
})
|
||||
.map(o => o.id);
|
||||
const addOptions = newOptions.filter(o => typeof o.id === 'number');
|
||||
|
||||
const { addSelectOptions, removeSelectOptions } =
|
||||
updateSelect(selectProperty);
|
||||
|
||||
deleteOptionIds.length &&
|
||||
(selectProperty = (await removeSelectOptions(
|
||||
...deleteOptionIds
|
||||
)) as SelectPropertyType);
|
||||
|
||||
addOptions.length &&
|
||||
(selectProperty = (await addSelectOptions(
|
||||
...(addOptions as unknown as Omit<SelectOption, 'id'>[])
|
||||
)) as SelectPropertyType);
|
||||
|
||||
const selectedId = getOfficialSelected({
|
||||
isMulti: type === PendantTypes.MultiSelect,
|
||||
options: selectProperty.options,
|
||||
tempOptions: newPropertyItem,
|
||||
tempSelectedId: newValue,
|
||||
});
|
||||
|
||||
await setPendant(selectProperty, selectedId);
|
||||
} else if (type === PendantTypes.Information) {
|
||||
// const { emailOptions, phoneOptions, locationOptions } =
|
||||
// property as InformationProperty;
|
||||
const optionGroup = newPropertyItem as TempInformationType;
|
||||
|
||||
const emailOptions = optionGroup.emailOptions.map(option => {
|
||||
if (typeof option.id === 'number') {
|
||||
option.id = genSelectOptionId();
|
||||
}
|
||||
return option;
|
||||
});
|
||||
const phoneOptions = optionGroup.phoneOptions.map(option => {
|
||||
if (typeof option.id === 'number') {
|
||||
option.id = genSelectOptionId();
|
||||
}
|
||||
return option;
|
||||
});
|
||||
const locationOptions = optionGroup.locationOptions.map(option => {
|
||||
if (typeof option.id === 'number') {
|
||||
option.id = genSelectOptionId();
|
||||
}
|
||||
return option;
|
||||
});
|
||||
|
||||
const newProperty = await updateProperty({
|
||||
...property,
|
||||
emailOptions,
|
||||
phoneOptions,
|
||||
locationOptions,
|
||||
} as InformationProperty);
|
||||
|
||||
await setPendant(newProperty, {
|
||||
email: getOfficialSelected({
|
||||
isMulti: true,
|
||||
options: emailOptions as SelectOption[],
|
||||
tempOptions: newPropertyItem.emailOptions,
|
||||
tempSelectedId: newValue.email,
|
||||
}),
|
||||
phone: getOfficialSelected({
|
||||
isMulti: true,
|
||||
options: phoneOptions as SelectOption[],
|
||||
tempOptions: newPropertyItem.phoneOptions,
|
||||
tempSelectedId: newValue.phone,
|
||||
}),
|
||||
location: getOfficialSelected({
|
||||
isMulti: true,
|
||||
options: locationOptions as SelectOption[],
|
||||
tempOptions: newPropertyItem.locationOptions,
|
||||
tempSelectedId: newValue.location,
|
||||
}),
|
||||
});
|
||||
} else {
|
||||
await setPendant(property, newValue);
|
||||
}
|
||||
|
||||
if (fieldName !== property.name) {
|
||||
await updateProperty({
|
||||
...property,
|
||||
name: fieldName,
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -1,4 +1,5 @@
|
||||
import {
|
||||
PropertyType,
|
||||
RecastBlockValue,
|
||||
RecastPropertyId,
|
||||
SelectOption,
|
||||
@@ -175,3 +176,49 @@ export const genInitialOptions = (
|
||||
}
|
||||
return [genBasicOption({ index: 0, iconConfig })];
|
||||
};
|
||||
|
||||
export const checkPendantForm = (
|
||||
type: PropertyType,
|
||||
fieldTitle: string,
|
||||
newProperty: any,
|
||||
newValue: any
|
||||
): { passed: boolean; message: string } => {
|
||||
if (!fieldTitle) {
|
||||
return { passed: false, message: 'The field title cannot be empty !' };
|
||||
}
|
||||
|
||||
if (
|
||||
type === PendantTypes.MultiSelect ||
|
||||
type === PendantTypes.Select ||
|
||||
type === PendantTypes.Status
|
||||
) {
|
||||
if (!newProperty) {
|
||||
return {
|
||||
passed: false,
|
||||
message: 'Ensure at least one non-empty option !',
|
||||
};
|
||||
}
|
||||
}
|
||||
if (type === PendantTypes.Information) {
|
||||
if (!newProperty) {
|
||||
return {
|
||||
passed: false,
|
||||
message: 'Ensure at least one non-empty option !',
|
||||
};
|
||||
}
|
||||
}
|
||||
if (
|
||||
type === PendantTypes.Text ||
|
||||
type === PendantTypes.Date ||
|
||||
type === PendantTypes.Mention
|
||||
) {
|
||||
if (!newValue) {
|
||||
return {
|
||||
passed: false,
|
||||
message: `The content of the input must not be empty !`,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return { passed: true, message: 'Check passed !' };
|
||||
};
|
||||
|
||||
@@ -297,10 +297,10 @@ export class ScrollManager {
|
||||
}
|
||||
|
||||
public lock() {
|
||||
this._scrollController.lockScroll();
|
||||
this._scrollController?.lockScroll();
|
||||
}
|
||||
|
||||
public unLock() {
|
||||
this._scrollController.unLockScroll();
|
||||
this._scrollController?.unLockScroll();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import type { DragDropManager } from './drag-drop';
|
||||
import { MouseManager } from './mouse';
|
||||
import { Observable } from 'rxjs';
|
||||
import { Point } from '@toeverything/utils';
|
||||
import { ScrollManager } from './scroll';
|
||||
|
||||
// import { BrowserClipboard } from './clipboard/browser-clipboard';
|
||||
|
||||
@@ -63,6 +64,7 @@ export interface VirgoSelection {
|
||||
// Editor's external API
|
||||
export interface Virgo {
|
||||
selectionManager: SelectionManager;
|
||||
scrollManager: ScrollManager;
|
||||
createBlock: (
|
||||
type: keyof BlockFlavors,
|
||||
parentId?: string
|
||||
|
||||
@@ -40,26 +40,30 @@ export const CommandMenu = ({ editor, hooks, style }: CommandMenuProps) => {
|
||||
bottom: 0,
|
||||
});
|
||||
|
||||
const [search_text, set_search_text] = useState<string>('');
|
||||
const [search_blocks, set_search_blocks] = useState<QueryResult>([]);
|
||||
const [searchText, setSearchText] = useState<string>('');
|
||||
const [searchBlocks, setSearchBlocks] = useState<QueryResult>([]);
|
||||
const commandMenuContentRef = useRef();
|
||||
// TODO: Two-way link to be developed
|
||||
// useEffect(() => {
|
||||
// QueryBlocks(editor, search_text, result => set_search_blocks(result));
|
||||
// }, [editor, search_text]);
|
||||
// QueryBlocks(editor, searchText, result => set_searchBlocks(result));
|
||||
// }, [editor, searchText]);
|
||||
|
||||
const hideMenu = () => {
|
||||
setShow(false);
|
||||
editor.scrollManager.unLock();
|
||||
};
|
||||
const [types, categories] = useMemo(() => {
|
||||
const types: Array<BlockFlavorKeys | string> = [];
|
||||
const categories: Array<CommandMenuCategories> = [];
|
||||
if (search_blocks.length) {
|
||||
Object.values(search_blocks).forEach(({ id }) => types.push(id));
|
||||
if (searchBlocks.length) {
|
||||
Object.values(searchBlocks).forEach(({ id }) => types.push(id));
|
||||
categories.push(CommandMenuCategories.pages);
|
||||
}
|
||||
Object.entries(menuItemsMap).forEach(([category, itemInfoList]) => {
|
||||
itemInfoList.forEach(info => {
|
||||
if (
|
||||
!search_text ||
|
||||
info.text.toLowerCase().includes(search_text.toLowerCase())
|
||||
!searchText ||
|
||||
info.text.toLowerCase().includes(searchText.toLowerCase())
|
||||
) {
|
||||
types.push(info.type);
|
||||
}
|
||||
@@ -73,9 +77,9 @@ export const CommandMenu = ({ editor, hooks, style }: CommandMenuProps) => {
|
||||
});
|
||||
});
|
||||
return [types, categories];
|
||||
}, [search_blocks, search_text]);
|
||||
}, [searchBlocks, searchText]);
|
||||
|
||||
const check_if_show_command_menu = useCallback(
|
||||
const checkIfShowCommandMenu = useCallback(
|
||||
async (event: React.KeyboardEvent<HTMLDivElement>) => {
|
||||
const { type, anchorNode } = editor.selection.currentSelectInfo;
|
||||
if (event.key === '/' && type === 'Range') {
|
||||
@@ -101,8 +105,9 @@ export const CommandMenu = ({ editor, hooks, style }: CommandMenuProps) => {
|
||||
);
|
||||
}
|
||||
});
|
||||
set_search_text('');
|
||||
setSearchText('');
|
||||
setShow(true);
|
||||
editor.scrollManager.lock();
|
||||
const rect =
|
||||
editor.selection.currentSelectInfo?.browserSelection
|
||||
?.getRangeAt(0)
|
||||
@@ -137,12 +142,12 @@ export const CommandMenu = ({ editor, hooks, style }: CommandMenuProps) => {
|
||||
[editor, blockId]
|
||||
);
|
||||
|
||||
const handle_click_others = useCallback(
|
||||
const handleClickOthers = useCallback(
|
||||
(event: React.KeyboardEvent<HTMLDivElement>) => {
|
||||
if (show) {
|
||||
const { anchorNode } = editor.selection.currentSelectInfo;
|
||||
if (anchorNode.id !== blockId) {
|
||||
setShow(false);
|
||||
hideMenu();
|
||||
return;
|
||||
}
|
||||
setTimeout(() => {
|
||||
@@ -150,12 +155,12 @@ export const CommandMenu = ({ editor, hooks, style }: CommandMenuProps) => {
|
||||
editor.blockHelper.getSearchSlashText(blockId);
|
||||
// check if has search text
|
||||
if (searchText && searchText.startsWith('/')) {
|
||||
set_search_text(searchText.slice(1));
|
||||
setSearchText(searchText.slice(1));
|
||||
} else {
|
||||
setShow(false);
|
||||
hideMenu();
|
||||
}
|
||||
if (searchText.length > 6 && !types.length) {
|
||||
setShow(false);
|
||||
hideMenu();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -163,18 +168,18 @@ export const CommandMenu = ({ editor, hooks, style }: CommandMenuProps) => {
|
||||
[editor, show, blockId, types]
|
||||
);
|
||||
|
||||
const handle_keyup = useCallback(
|
||||
const handleKeyup = useCallback(
|
||||
(event: React.KeyboardEvent<HTMLDivElement>) => {
|
||||
check_if_show_command_menu(event);
|
||||
handle_click_others(event);
|
||||
checkIfShowCommandMenu(event);
|
||||
handleClickOthers(event);
|
||||
},
|
||||
[check_if_show_command_menu, handle_click_others]
|
||||
[checkIfShowCommandMenu, handleClickOthers]
|
||||
);
|
||||
|
||||
const handle_key_down = useCallback(
|
||||
const handleKeyDown = useCallback(
|
||||
(event: React.KeyboardEvent<HTMLDivElement>) => {
|
||||
if (event.code === 'Escape') {
|
||||
setShow(false);
|
||||
hideMenu();
|
||||
}
|
||||
},
|
||||
[]
|
||||
@@ -183,23 +188,23 @@ export const CommandMenu = ({ editor, hooks, style }: CommandMenuProps) => {
|
||||
useEffect(() => {
|
||||
const sub = hooks
|
||||
.get(HookType.ON_ROOT_NODE_KEYUP)
|
||||
.subscribe(handle_keyup);
|
||||
.subscribe(handleKeyup);
|
||||
sub.add(
|
||||
hooks
|
||||
.get(HookType.ON_ROOT_NODE_KEYDOWN_CAPTURE)
|
||||
.subscribe(handle_key_down)
|
||||
.subscribe(handleKeyDown)
|
||||
);
|
||||
|
||||
return () => {
|
||||
sub.unsubscribe();
|
||||
};
|
||||
}, [handle_keyup, handle_key_down, hooks]);
|
||||
}, [handleKeyup, handleKeyDown, hooks]);
|
||||
|
||||
const handle_click_away = () => {
|
||||
setShow(false);
|
||||
const handleClickAway = () => {
|
||||
hideMenu();
|
||||
};
|
||||
|
||||
const handle_selected = async (type: BlockFlavorKeys | string) => {
|
||||
const handleSelected = async (type: BlockFlavorKeys | string) => {
|
||||
const text = await editor.commands.textCommands.getBlockText(blockId);
|
||||
editor.blockHelper.removeSearchSlash(blockId, true);
|
||||
if (type.startsWith('Virgo')) {
|
||||
@@ -224,21 +229,21 @@ export const CommandMenu = ({ editor, hooks, style }: CommandMenuProps) => {
|
||||
block.firstCreateFlag = true;
|
||||
}
|
||||
}
|
||||
setShow(false);
|
||||
hideMenu();
|
||||
};
|
||||
|
||||
const handle_close = () => {
|
||||
const handleClose = () => {
|
||||
editor.blockHelper.removeSearchSlash(blockId);
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{ zIndex: 1 }}
|
||||
onKeyUpCapture={handle_keyup}
|
||||
onKeyUpCapture={handleKeyup}
|
||||
ref={commandMenuContentRef}
|
||||
>
|
||||
{show ? (
|
||||
<MuiClickAwayListener onClickAway={handle_click_away}>
|
||||
<MuiClickAwayListener onClickAway={handleClickAway}>
|
||||
<div>
|
||||
<CommandMenuContainer
|
||||
editor={editor}
|
||||
@@ -249,9 +254,9 @@ export const CommandMenu = ({ editor, hooks, style }: CommandMenuProps) => {
|
||||
}}
|
||||
isShow={show}
|
||||
blockId={blockId}
|
||||
onSelected={handle_selected}
|
||||
onclose={handle_close}
|
||||
searchBlocks={search_blocks}
|
||||
onSelected={handleSelected}
|
||||
onclose={handleClose}
|
||||
searchBlocks={searchBlocks}
|
||||
types={types}
|
||||
categories={categories}
|
||||
/>
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill-rule="evenodd" d="M11.04 12.96V18h1.92v-5.04H18v-1.92h-5.04V6h-1.92v5.04H6v1.92h5.04Z" clip-rule="evenodd"/></svg>
|
||||
|
After Width: | Height: | Size: 185 B |
@@ -0,0 +1,21 @@
|
||||
|
||||
import { FC } from 'react';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { SvgIcon } from '@mui/material';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import type { SvgIconProps } from '@mui/material';
|
||||
|
||||
export interface AddIconProps extends Omit<SvgIconProps, 'color'> {
|
||||
color?: string
|
||||
}
|
||||
|
||||
export const AddIcon: FC<AddIconProps> = ({ color, style, ...props}) => {
|
||||
const propsStyles = {"color": color};
|
||||
const customStyles = {};
|
||||
const styles = {...propsStyles, ...customStyles, ...style}
|
||||
return (
|
||||
<SvgIcon style={styles} {...props}>
|
||||
<path fillRule="evenodd" d="M11.04 12.96V18h1.92v-5.04H18v-1.92h-5.04V6h-1.92v5.04H6v1.92h5.04Z" clipRule="evenodd" />
|
||||
</SvgIcon>
|
||||
)
|
||||
};
|
||||
@@ -1,4 +1,4 @@
|
||||
export const timestamp = 1659000896562;
|
||||
export const timestamp = 1659423582387;
|
||||
export * from './image/image';
|
||||
export * from './format-clear/format-clear';
|
||||
export * from './backward-undo/backward-undo';
|
||||
@@ -125,4 +125,8 @@ export * from './eraser/eraser';
|
||||
export * from './group-by/group-by';
|
||||
export * from './layout/layout';
|
||||
export * from './lock/lock';
|
||||
export * from './unlock/unlock';
|
||||
export * from './unlock/unlock';
|
||||
export * from './more-tags-an-subblocks/more-tags-an-subblocks';
|
||||
export * from './page-in-page-tree/page-in-page-tree';
|
||||
export * from './pin/pin';
|
||||
export * from './add/add';
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill-rule="evenodd" d="M2.2 3v13A3.8 3.8 0 0 0 6 19.8h10.316A2.801 2.801 0 0 0 21.8 19a2.8 2.8 0 0 0-5.484-.8H6A2.2 2.2 0 0 1 3.8 16v-6h12.584a2.801 2.801 0 1 0-.12-1.6H3.8V3H2.2ZM19 7.8a1.2 1.2 0 1 0 0 2.4 1.2 1.2 0 0 0 0-2.4ZM17.8 19a1.2 1.2 0 1 1 2.4 0 1.2 1.2 0 0 1-2.4 0Z" clip-rule="evenodd"/></svg>
|
||||
|
After Width: | Height: | Size: 371 B |
+21
@@ -0,0 +1,21 @@
|
||||
|
||||
import { FC } from 'react';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { SvgIcon } from '@mui/material';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import type { SvgIconProps } from '@mui/material';
|
||||
|
||||
export interface MoreTagsAnSubblocksIconProps extends Omit<SvgIconProps, 'color'> {
|
||||
color?: string
|
||||
}
|
||||
|
||||
export const MoreTagsAnSubblocksIcon: FC<MoreTagsAnSubblocksIconProps> = ({ color, style, ...props}) => {
|
||||
const propsStyles = {"color": color};
|
||||
const customStyles = {};
|
||||
const styles = {...propsStyles, ...customStyles, ...style}
|
||||
return (
|
||||
<SvgIcon style={styles} {...props}>
|
||||
<path fillRule="evenodd" d="M2.2 3v13A3.8 3.8 0 0 0 6 19.8h10.316A2.801 2.801 0 0 0 21.8 19a2.8 2.8 0 0 0-5.484-.8H6A2.2 2.2 0 0 1 3.8 16v-6h12.584a2.801 2.801 0 1 0-.12-1.6H3.8V3H2.2ZM19 7.8a1.2 1.2 0 1 0 0 2.4 1.2 1.2 0 0 0 0-2.4ZM17.8 19a1.2 1.2 0 1 1 2.4 0 1.2 1.2 0 0 1-2.4 0Z" clipRule="evenodd" />
|
||||
</SvgIcon>
|
||||
)
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><circle cx="12" cy="12" r="2.5"/></svg>
|
||||
|
After Width: | Height: | Size: 99 B |
@@ -0,0 +1,21 @@
|
||||
|
||||
import { FC } from 'react';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { SvgIcon } from '@mui/material';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import type { SvgIconProps } from '@mui/material';
|
||||
|
||||
export interface PageInPageTreeIconProps extends Omit<SvgIconProps, 'color'> {
|
||||
color?: string
|
||||
}
|
||||
|
||||
export const PageInPageTreeIcon: FC<PageInPageTreeIconProps> = ({ color, style, ...props}) => {
|
||||
const propsStyles = {"color": color};
|
||||
const customStyles = {};
|
||||
const styles = {...propsStyles, ...customStyles, ...style}
|
||||
return (
|
||||
<SvgIcon style={styles} {...props}>
|
||||
<circle cx={12} cy={12} r={2.5} />
|
||||
</SvgIcon>
|
||||
)
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><g clip-path="url(#a)"><path fill-rule="evenodd" d="M20.403 7.84a2 2 0 0 1 0 2.828l-2.12 2.12-1.414-1.414-2.596 2.595a6.003 6.003 0 0 1-1.172 6.83l-4.243-4.243-3.834 3.834a1 1 0 1 1-1.414-1.414l3.834-3.834L3.2 10.9a6.002 6.002 0 0 1 6.83-1.173l2.595-2.596-1.414-1.414 2.12-2.12a2 2 0 0 1 2.829 0l4.242 4.242Z" clip-rule="evenodd"/></g><defs><clipPath id="a"><path d="M0 0H24V24H0z" transform="matrix(-1 0 0 1 24 0)"/></clipPath></defs></svg>
|
||||
|
After Width: | Height: | Size: 501 B |
@@ -0,0 +1,21 @@
|
||||
|
||||
import { FC } from 'react';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { SvgIcon } from '@mui/material';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import type { SvgIconProps } from '@mui/material';
|
||||
|
||||
export interface PinIconProps extends Omit<SvgIconProps, 'color'> {
|
||||
color?: string
|
||||
}
|
||||
|
||||
export const PinIcon: FC<PinIconProps> = ({ color, style, ...props}) => {
|
||||
const propsStyles = {"color": color};
|
||||
const customStyles = {};
|
||||
const styles = {...propsStyles, ...customStyles, ...style}
|
||||
return (
|
||||
<SvgIcon style={styles} {...props}>
|
||||
<g clipPath="url(#a)"><path fillRule="evenodd" d="M20.403 7.84a2 2 0 0 1 0 2.828l-2.12 2.12-1.414-1.414-2.596 2.595a6.003 6.003 0 0 1-1.172 6.83l-4.243-4.243-3.834 3.834a1 1 0 1 1-1.414-1.414l3.834-3.834L3.2 10.9a6.002 6.002 0 0 1 6.83-1.173l2.595-2.596-1.414-1.414 2.12-2.12a2 2 0 0 1 2.829 0l4.242 4.242Z" clipRule="evenodd" /></g><defs><clipPath id="a"><path d="M0 0H24V24H0z" transform="matrix(-1 0 0 1 24 0)" /></clipPath></defs>
|
||||
</SvgIcon>
|
||||
)
|
||||
};
|
||||
@@ -3,13 +3,14 @@ import {
|
||||
LogoIcon,
|
||||
SideBarViewIcon,
|
||||
SearchIcon,
|
||||
SideBarViewCloseIcon,
|
||||
} from '@toeverything/components/icons';
|
||||
import { useShowSettingsSidebar } from '@toeverything/datasource/state';
|
||||
import { CurrentPageTitle } from './Title';
|
||||
import { EditorBoardSwitcher } from './EditorBoardSwitcher';
|
||||
|
||||
export const LayoutHeader = () => {
|
||||
const { toggleSettingsSidebar: toggleInfoSidebar } =
|
||||
const { toggleSettingsSidebar: toggleInfoSidebar, showSettingsSidebar } =
|
||||
useShowSettingsSidebar();
|
||||
|
||||
return (
|
||||
@@ -31,7 +32,11 @@ export const LayoutHeader = () => {
|
||||
</div>
|
||||
|
||||
<IconButton onClick={toggleInfoSidebar} size="large">
|
||||
<SideBarViewIcon />
|
||||
{showSettingsSidebar ? (
|
||||
<SideBarViewIcon />
|
||||
) : (
|
||||
<SideBarViewCloseIcon />
|
||||
)}
|
||||
</IconButton>
|
||||
</StyledHelper>
|
||||
</FlexContainer>
|
||||
|
||||
@@ -50,31 +50,35 @@ export const Activities = () => {
|
||||
const [recentPages, setRecentPages] = useState([]);
|
||||
const userId = user?.id;
|
||||
|
||||
const fetchRecentPages = useCallback(async () => {
|
||||
if (!userId || !currentSpaceId) {
|
||||
return;
|
||||
}
|
||||
const recent_pages = await services.api.userConfig.getRecentPages(
|
||||
currentSpaceId,
|
||||
userId
|
||||
);
|
||||
setRecentPages(recent_pages);
|
||||
}, [userId, currentSpaceId]);
|
||||
/* temporarily remove:show recently viewed documents */
|
||||
// const fetchRecentPages = useCallback(async () => {
|
||||
// if (!userId || !currentSpaceId) {
|
||||
// return;
|
||||
// }
|
||||
// const recent_pages = await services.api.userConfig.getRecentPages(
|
||||
// currentSpaceId,
|
||||
// userId
|
||||
// );
|
||||
// setRecentPages(recent_pages);
|
||||
// }, [userId, currentSpaceId]);
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
await fetchRecentPages();
|
||||
})();
|
||||
}, [fetchRecentPages]);
|
||||
// useEffect(() => {
|
||||
// (async () => {
|
||||
// await fetchRecentPages();
|
||||
// })();
|
||||
// }, [fetchRecentPages]);
|
||||
|
||||
/* show recently edit documents */
|
||||
const getRecentEditPages = async (state, block) => {
|
||||
console.log(state, await block.children());
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
let unobserve: () => void;
|
||||
const observe = async () => {
|
||||
unobserve = await services.api.userConfig.observe(
|
||||
{ workspace: currentSpaceId },
|
||||
() => {
|
||||
fetchRecentPages();
|
||||
}
|
||||
getRecentEditPages
|
||||
);
|
||||
};
|
||||
observe();
|
||||
@@ -82,7 +86,7 @@ export const Activities = () => {
|
||||
return () => {
|
||||
unobserve?.();
|
||||
};
|
||||
}, [currentSpaceId, fetchRecentPages]);
|
||||
}, [currentSpaceId, getRecentEditPages]);
|
||||
|
||||
return (
|
||||
<StyledWrapper>
|
||||
|
||||
@@ -95,30 +95,37 @@ export function DndTree(props: DndTreeProps) {
|
||||
>
|
||||
{/* <button onClick={() => handleAdd()}> add top node</button> */}
|
||||
{flattenedItems.map(
|
||||
({ id, title, children, collapsed, depth }) => (
|
||||
<DndTreeItem
|
||||
key={id}
|
||||
id={id}
|
||||
// value={id}
|
||||
value={title}
|
||||
collapsed={Boolean(collapsed && children.length)}
|
||||
depth={
|
||||
id === activeId && projected
|
||||
? projected.depth
|
||||
: depth
|
||||
}
|
||||
indentationWidth={indentationWidth}
|
||||
indicator={showDragIndicator}
|
||||
onCollapse={
|
||||
collapsible && children.length
|
||||
? () => handleCollapse(id)
|
||||
: undefined
|
||||
}
|
||||
onRemove={
|
||||
removable ? () => handleRemove(id) : undefined
|
||||
}
|
||||
/>
|
||||
)
|
||||
({ id, title, children, collapsed, depth }) => {
|
||||
return (
|
||||
<DndTreeItem
|
||||
key={id}
|
||||
id={id}
|
||||
// value={id}
|
||||
value={title}
|
||||
collapsed={Boolean(
|
||||
collapsed && children.length
|
||||
)}
|
||||
depth={
|
||||
id === activeId && projected
|
||||
? projected.depth
|
||||
: depth
|
||||
}
|
||||
indentationWidth={indentationWidth}
|
||||
indicator={showDragIndicator}
|
||||
childCount={children.length}
|
||||
onCollapse={
|
||||
collapsible && children.length
|
||||
? () => handleCollapse(id)
|
||||
: undefined
|
||||
}
|
||||
onRemove={
|
||||
removable
|
||||
? () => handleRemove(id)
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
)}
|
||||
<DragOverlay
|
||||
dropAnimation={dropAnimation}
|
||||
|
||||
@@ -4,6 +4,9 @@ import {
|
||||
Cascader,
|
||||
CascaderItemProps,
|
||||
MuiDivider as Divider,
|
||||
MuiClickAwayListener as ClickAwayListener,
|
||||
IconButton,
|
||||
styled,
|
||||
} from '@toeverything/components/ui';
|
||||
import React from 'react';
|
||||
import { NavLink, useNavigate } from 'react-router-dom';
|
||||
@@ -11,6 +14,8 @@ import { copyToClipboard } from '@toeverything/utils';
|
||||
import { services, TemplateFactory } from '@toeverything/datasource/db-service';
|
||||
import { NewFromTemplatePortal } from './NewFromTemplatePortal';
|
||||
import { useFlag } from '@toeverything/datasource/feature-flags';
|
||||
import { MoreIcon, AddIcon } from '@toeverything/components/icons';
|
||||
|
||||
const MESSAGES = {
|
||||
COPY_LINK_SUCCESS: 'Copyed link to clipboard',
|
||||
};
|
||||
@@ -19,6 +24,12 @@ interface ActionsProps {
|
||||
pageId: string;
|
||||
onRemove: () => void;
|
||||
}
|
||||
|
||||
const StyledAction = styled('div')({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
});
|
||||
|
||||
function DndTreeItemMoreActions(props: ActionsProps) {
|
||||
const [alert_open, set_alert_open] = React.useState(false);
|
||||
const [anchorEl, setAnchorEl] = React.useState<HTMLDivElement | null>(null);
|
||||
@@ -233,29 +244,42 @@ function DndTreeItemMoreActions(props: ActionsProps) {
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<span
|
||||
className={styles['TreeItemMoreActions']}
|
||||
onClick={handleClick}
|
||||
>
|
||||
···
|
||||
</span>
|
||||
<Cascader
|
||||
items={menuList}
|
||||
anchorEl={anchorEl}
|
||||
placement="right-start"
|
||||
open={open}
|
||||
onClose={handleClose}
|
||||
></Cascader>
|
||||
<Snackbar
|
||||
anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
|
||||
open={alert_open}
|
||||
message={MESSAGES.COPY_LINK_SUCCESS}
|
||||
key={'bottomcenter'}
|
||||
autoHideDuration={2000}
|
||||
onClose={handle_alert_close}
|
||||
/>
|
||||
</>
|
||||
<ClickAwayListener onClickAway={() => handleClose()}>
|
||||
<div>
|
||||
<div className={styles['TreeItemMoreActions']}>
|
||||
<StyledAction>
|
||||
<IconButton
|
||||
size="small"
|
||||
onClick={handle_new_child_page}
|
||||
>
|
||||
<AddIcon />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
size="small"
|
||||
hoverColor="#E0E6EB"
|
||||
onClick={handleClick}
|
||||
>
|
||||
<MoreIcon />
|
||||
</IconButton>
|
||||
</StyledAction>
|
||||
</div>
|
||||
<Cascader
|
||||
items={menuList}
|
||||
anchorEl={anchorEl}
|
||||
placement="right-start"
|
||||
open={open}
|
||||
onClose={handleClose}
|
||||
></Cascader>
|
||||
<Snackbar
|
||||
anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
|
||||
open={alert_open}
|
||||
message={MESSAGES.COPY_LINK_SUCCESS}
|
||||
key={'bottomcenter'}
|
||||
autoHideDuration={2000}
|
||||
onClose={handle_alert_close}
|
||||
/>
|
||||
</div>
|
||||
</ClickAwayListener>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -65,7 +65,7 @@ export const TreeItem = forwardRef<HTMLDivElement, TreeItemProps>(
|
||||
const navigate = useNavigate();
|
||||
const BooleanPageTreeItemMoreActions = useFlag(
|
||||
'BooleanPageTreeItemMoreActions',
|
||||
false
|
||||
true
|
||||
);
|
||||
return (
|
||||
<li
|
||||
@@ -94,7 +94,12 @@ export const TreeItem = forwardRef<HTMLDivElement, TreeItemProps>(
|
||||
title={value}
|
||||
>
|
||||
<Action onClick={onCollapse}>
|
||||
{collapsed ? <ArrowRightIcon /> : <ArrowDropDownIcon />}
|
||||
{childCount !== 0 &&
|
||||
(collapsed ? (
|
||||
<ArrowRightIcon />
|
||||
) : (
|
||||
<ArrowDropDownIcon />
|
||||
))}
|
||||
</Action>
|
||||
{/*<Action>*/}
|
||||
{/* <DocumentIcon />*/}
|
||||
|
||||
+1
-1
@@ -83,7 +83,7 @@
|
||||
justify-content: space-around;
|
||||
background-color: #fff;
|
||||
color: #4c6275;
|
||||
padding: 0 0.5rem;
|
||||
padding-right: 0.5rem;
|
||||
overflow: hidden;
|
||||
|
||||
.TreeItemMoreActions {
|
||||
|
||||
@@ -36,6 +36,7 @@ interface IconButtonProps {
|
||||
style?: CSSProperties;
|
||||
className?: string;
|
||||
size?: SizeType;
|
||||
hoverColor?: string;
|
||||
}
|
||||
|
||||
export const IconButton: FC<PropsWithChildren<IconButtonProps>> = ({
|
||||
@@ -57,47 +58,48 @@ export const IconButton: FC<PropsWithChildren<IconButtonProps>> = ({
|
||||
);
|
||||
};
|
||||
|
||||
const Container = styled('button')<{ size?: SizeType }>(
|
||||
({ theme, size = SIZE_MIDDLE }) => {
|
||||
const { iconSize, areaSize } = SIZE_CONFIG[size];
|
||||
const Container = styled('button')<{
|
||||
size?: SizeType;
|
||||
hoverColor?: string;
|
||||
}>(({ theme, size = SIZE_MIDDLE, hoverColor }) => {
|
||||
const { iconSize, areaSize } = SIZE_CONFIG[size];
|
||||
|
||||
return {
|
||||
return {
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
width: areaSize,
|
||||
height: areaSize,
|
||||
backgroundColor: 'transparent',
|
||||
color: theme.affine.palette.icons,
|
||||
padding: theme.affine.spacing.iconPadding,
|
||||
borderRadius: '3px',
|
||||
|
||||
'& svg': {
|
||||
width: iconSize,
|
||||
height: iconSize,
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
width: areaSize,
|
||||
height: areaSize,
|
||||
backgroundColor: theme.affine.palette.white,
|
||||
color: theme.affine.palette.icons,
|
||||
padding: theme.affine.spacing.iconPadding,
|
||||
borderRadius: '5px',
|
||||
},
|
||||
|
||||
'& svg': {
|
||||
width: iconSize,
|
||||
height: iconSize,
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
'&:hover': {
|
||||
backgroundColor: hoverColor || theme.affine.palette.hover,
|
||||
},
|
||||
|
||||
'&:hover': {
|
||||
backgroundColor: theme.affine.palette.hover,
|
||||
},
|
||||
[`&${buttonStatus.hover}`]: {
|
||||
backgroundColor: theme.affine.palette.hover,
|
||||
},
|
||||
|
||||
[`&${buttonStatus.hover}`]: {
|
||||
backgroundColor: theme.affine.palette.hover,
|
||||
},
|
||||
'&:focus': {
|
||||
color: theme.affine.palette.primary,
|
||||
},
|
||||
[`&.${buttonStatus.focus}`]: {
|
||||
color: theme.affine.palette.primary,
|
||||
},
|
||||
|
||||
'&:focus': {
|
||||
color: theme.affine.palette.primary,
|
||||
},
|
||||
[`&.${buttonStatus.focus}`]: {
|
||||
color: theme.affine.palette.primary,
|
||||
},
|
||||
|
||||
[`&${buttonStatus.disabled}`]: {
|
||||
cursor: 'not-allowed',
|
||||
},
|
||||
};
|
||||
}
|
||||
);
|
||||
[`&${buttonStatus.disabled}`]: {
|
||||
cursor: 'not-allowed',
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user