Compare commits

...

3 Commits

Author SHA1 Message Date
Ammar Ahmed
21f931d3ea mobile: fix empty toast message shows on removing tools from group 2026-04-14 08:50:11 +05:00
Ammar Ahmed
58253f11b7 mobile: fix tools and groups do not re-render properly on changing preset or adding tools 2026-04-10 14:03:22 +05:00
Ammar Ahmed
0535de9ae9 editor: make editor tools object a function
Required for string localization to work properly
2026-04-10 12:27:54 +05:00
9 changed files with 178 additions and 148 deletions

View File

@@ -24,7 +24,6 @@ import Animated, { FadeInDown, FadeOutDown } from "react-native-reanimated";
import { Button } from "../../../components/ui/button";
import { Notice } from "../../../components/ui/notice";
import Paragraph from "../../../components/ui/typography/paragraph";
import PremiumService from "../../../services/premium";
import { useThemeColors } from "@notesnook/theme";
import { AppFontSize } from "../../../utils/size";
import { Group } from "./group";
@@ -41,7 +40,7 @@ export const ConfigureToolbar = () => {
const renderGroups = () => {
return data?.map((item, index) => (
<Group key={`group-${index}`} item={item} index={index} />
<Group key={`group-${index}-${item.length}`} item={item} index={index} />
));
};
@@ -131,6 +130,7 @@ export const ConfigureToolbar = () => {
style={{
flex: 1
}}
key={preset}
scrollEventThrottle={13}
showsVerticalScrollIndicator={false}
>

View File

@@ -31,7 +31,7 @@ import { renderTool } from "./common";
import { DraggableItem, useDragState } from "./state";
import ToolSheet from "./tool-sheet";
import { useIsFeatureAvailable } from "@notesnook/common";
import { isFeatureAvailable, useIsFeatureAvailable } from "@notesnook/common";
import type { ToolId } from "@notesnook/editor";
import { strings } from "@notesnook/intl";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
@@ -64,78 +64,87 @@ export const Group = ({
const { colors } = useThemeColors();
const featureAvailable = useIsFeatureAvailable("customToolbarPreset");
const onDrop = (data: DraxDragWithReceiverEventData) => {
if (!featureAvailable?.isAllowed) {
ToastManager.show({
type: "info",
message: featureAvailable?.error
});
return;
}
const isDroppedAbove = data.receiver.receiveOffsetRatio.y < 0.5;
const dragged = data.dragged.payload;
const reciever = data.receiver.payload;
const _data = useDragState.getState().data.slice();
if (dragged.type === "group") {
const fromIndex = dragged.index;
const toIndex = isDroppedAbove
? Math.max(0, reciever.index)
: reciever.index + 1;
_data.splice(
toIndex > fromIndex ? toIndex - 1 : toIndex,
0,
_data.splice(fromIndex, 1)[0]
);
}
// Always insert sub group at the end of the group.
if (dragged.type === "subgroup") {
const fromIndex = dragged.index;
const insertAt = _data[reciever.index] as string[];
const insertFrom = _data[dragged.groupIndex] as string[];
if (typeof insertAt[insertAt.length - 1] !== "string") {
setRecieving(false);
return data.dragAbsolutePosition;
const onDrop = React.useCallback(
(data: DraxDragWithReceiverEventData) => {
if (!featureAvailable?.isAllowed) {
ToastManager.show({
type: "info",
message: featureAvailable?.error || strings.featureNotAvailable()
});
return;
}
insertAt.push(insertFrom.splice(fromIndex, 1)[0]);
}
const isDroppedAbove = data.receiver.receiveOffsetRatio.y < 0.5;
const dragged = data.dragged.payload;
const reciever = data.receiver.payload;
const _data = useDragState.getState().data.slice();
if (dragged.type === "tool") {
const insertFrom =
typeof dragged.parentIndex === "number"
? (_data[dragged.parentIndex][dragged.groupIndex] as string[])
: (_data[dragged.groupIndex] as string[]);
_data[groupIndex].push(insertFrom.splice(dragged.index, 1)[0] as ToolId);
}
if (dragged.type === "group") {
const fromIndex = dragged.index;
const toIndex = isDroppedAbove
? Math.max(0, reciever.index)
: reciever.index + 1;
setData(_data);
setRecieving(false);
return data.dragAbsolutePosition;
};
_data.splice(
toIndex > fromIndex ? toIndex - 1 : toIndex,
0,
_data.splice(fromIndex, 1)[0]
);
}
const onRecieveData = (data: DraxDragWithReceiverEventData) => {
setRecieving(true);
if (data.dragged.payload.type !== "group")
return setRecievePosition("below");
if (data.receiver.receiveOffsetRatio.y < 0.5) {
setRecievePosition("above");
} else {
setRecievePosition("below");
}
};
// Always insert sub group at the end of the group.
if (dragged.type === "subgroup") {
const fromIndex = dragged.index;
const insertAt = _data[reciever.index] as string[];
const insertFrom = _data[dragged.groupIndex] as string[];
if (typeof insertAt[insertAt.length - 1] !== "string") {
setRecieving(false);
return data.dragAbsolutePosition;
}
insertAt.push(insertFrom.splice(fromIndex, 1)[0]);
}
if (dragged.type === "tool") {
const insertFrom =
typeof dragged.parentIndex === "number"
? (_data[dragged.parentIndex][dragged.groupIndex] as string[])
: (_data[dragged.groupIndex] as string[]);
_data[groupIndex].push(
insertFrom.splice(dragged.index, 1)[0] as ToolId
);
}
setData(_data);
setRecieving(false);
return data.dragAbsolutePosition;
},
[featureAvailable]
);
const onRecieveData = React.useCallback(
(data: DraxDragWithReceiverEventData) => {
setRecieving(true);
if (data.dragged.payload.type !== "group")
return setRecievePosition("below");
if (data.receiver.receiveOffsetRatio.y < 0.5) {
setRecievePosition("above");
} else {
setRecievePosition("below");
}
},
[]
);
const buttons = [
{
name: "minus",
onPress: () => {
if (!featureAvailable?.isAllowed) {
onPress: async () => {
const feature = await isFeatureAvailable("customToolbarPreset");
if (!feature.isAllowed) {
ToastManager.show({
type: "info",
message: featureAvailable?.error
message: feature?.error
});
return;
}
@@ -157,11 +166,12 @@ export const Group = ({
},
{
name: "plus",
onPress: () => {
if (!featureAvailable?.isAllowed) {
onPress: async () => {
const feature = await isFeatureAvailable("customToolbarPreset");
if (!feature.isAllowed) {
ToastManager.show({
type: "info",
message: featureAvailable?.error
message: feature?.error
});
return;
}

View File

@@ -44,8 +44,8 @@ export default function ToolSheet({
fwdRef: RefObject<ActionSheetRef>;
}) {
const { colors } = useThemeColors();
const data = useDragState((state) => state.data);
const ungrouped = getUngroupedTools(data) as ToolId[];
const [data] = useDragState((state) => [state.data]);
const ungrouped = getUngroupedTools( data) as ToolId[];
const renderTool = React.useCallback(
(item: ToolId) => {
@@ -114,7 +114,7 @@ export default function ToolSheet({
return (
<View
style={{
maxHeight: "100%",
maxHeight: 400,
padding: DefaultAppStyles.GAP
}}
>

View File

@@ -33,7 +33,7 @@ import { DraggableItem, useDragState } from "./state";
import ToolSheet from "./tool-sheet";
import { findToolById, getToolIcon } from "./toolbar-definition";
import { useIsFeatureAvailable } from "@notesnook/common";
import { isFeatureAvailable, useIsFeatureAvailable } from "@notesnook/common";
import type { ToolId } from "@notesnook/editor";
import { strings } from "@notesnook/intl";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
@@ -78,11 +78,12 @@ export const Tool = ({
? [
{
name: "minus",
onPress: () => {
if (!featureAvailable?.isAllowed) {
onPress: async () => {
const feature = await isFeatureAvailable("customToolbarPreset");
if (!feature.isAllowed) {
ToastManager.show({
type: "info",
message: featureAvailable?.error
message: feature?.error
});
return;
}
@@ -102,11 +103,12 @@ export const Tool = ({
},
{
name: "plus",
onPress: () => {
if (!featureAvailable?.isAllowed) {
onPress: async () => {
const feature = await isFeatureAvailable("customToolbarPreset");
if (!feature.isAllowed) {
ToastManager.show({
type: "info",
message: featureAvailable?.error
message: feature?.error
});
return;
}
@@ -122,11 +124,12 @@ export const Tool = ({
: [
{
name: "minus",
onPress: () => {
if (!featureAvailable?.isAllowed) {
onPress: async () => {
const feature = await isFeatureAvailable("customToolbarPreset");
if (!feature.isAllowed) {
ToastManager.show({
type: "info",
message: featureAvailable?.error
message: feature?.error
});
return;
}
@@ -151,7 +154,7 @@ export const Tool = ({
if (parentIndex === undefined && !isSubgroup) {
btns.unshift({
name: "unfold-less-horizontal",
onPress: () => {
onPress: async () => {
if (groupIndex === undefined) return;
const _data = useDragState.getState().data.slice();
const hasSubGroup = Array.isArray(
@@ -267,6 +270,7 @@ export const Tool = ({
style={{
paddingLeft: 30
}}
key={`subgroup-${item.length}-${groupIndex}-${index}-${parentIndex}`}
>
{renderGroup({ index, item, parentIndex: groupIndex })}
</View>
@@ -290,63 +294,69 @@ export const Tool = ({
]
);
const onDrop = (data: DraxDragWithReceiverEventData) => {
if (!featureAvailable?.isAllowed) {
ToastManager.show({
type: "info",
message: featureAvailable?.error
});
return;
}
const isDroppedAbove = data.receiver.receiveOffsetRatio.y < 0.5;
const dragged = data.dragged.payload;
const reciever = data.receiver.payload;
const _data = useDragState.getState().data?.slice();
if (!_data) return;
const isFromSubgroup = typeof dragged?.parentIndex === "number";
const isDroppedAtSubgroup = typeof reciever?.parentIndex === "number";
if (dragged.type === "tool") {
const fromIndex = dragged.index;
const toIndex = isDroppedAbove
? Math.max(0, reciever.index)
: reciever.index + 1;
const insertAt = isDroppedAtSubgroup
? (_data[reciever.parentIndex][reciever.groupIndex] as string[])
: (_data[reciever.groupIndex] as string[]);
const insertFrom = isFromSubgroup
? (_data[dragged.parentIndex][dragged.groupIndex] as string[])
: (_data[dragged.groupIndex] as string[]);
insertAt.splice(
toIndex > fromIndex ? toIndex - 1 : toIndex,
0,
insertFrom.splice(fromIndex, 1)[0]
);
// Remove the group or subgroup if it is empty.
if (insertFrom.length === 0) {
isFromSubgroup
? _data[dragged.parentIndex].splice(
_data[dragged.parentIndex].length - 1,
1
)
: _data.splice(dragged.groupIndex, 1);
const onDrop = React.useCallback(
(data: DraxDragWithReceiverEventData) => {
if (!featureAvailable?.isAllowed) {
ToastManager.show({
type: "info",
message: featureAvailable?.error || strings.featureNotAvailable()
});
return;
}
}
setData(_data);
setRecieving(false);
return data.dragAbsolutePosition;
};
const isDroppedAbove = data.receiver.receiveOffsetRatio.y < 0.5;
const dragged = data.dragged.payload;
const reciever = data.receiver.payload;
const _data = useDragState.getState().data?.slice();
if (!_data) return;
const isFromSubgroup = typeof dragged?.parentIndex === "number";
const isDroppedAtSubgroup = typeof reciever?.parentIndex === "number";
const onRecieveData = (data: DraxDragWithReceiverEventData) => {
setRecieving(true);
if (data.receiver.receiveOffsetRatio.y < 0.5) {
setRecievePosition("above");
} else {
setRecievePosition("below");
}
};
if (dragged.type === "tool") {
const fromIndex = dragged.index;
const toIndex = isDroppedAbove
? Math.max(0, reciever.index)
: reciever.index + 1;
const insertAt = isDroppedAtSubgroup
? (_data[reciever.parentIndex][reciever.groupIndex] as string[])
: (_data[reciever.groupIndex] as string[]);
const insertFrom = isFromSubgroup
? (_data[dragged.parentIndex][dragged.groupIndex] as string[])
: (_data[dragged.groupIndex] as string[]);
insertAt.splice(
toIndex > fromIndex ? toIndex - 1 : toIndex,
0,
insertFrom.splice(fromIndex, 1)[0]
);
// Remove the group or subgroup if it is empty.
if (insertFrom.length === 0) {
isFromSubgroup
? _data[dragged.parentIndex].splice(
_data[dragged.parentIndex].length - 1,
1
)
: _data.splice(dragged.groupIndex, 1);
}
}
setData(_data);
setRecieving(false);
return data.dragAbsolutePosition;
},
[featureAvailable]
);
const onRecieveData = React.useCallback(
(data: DraxDragWithReceiverEventData) => {
setRecieving(true);
if (data.receiver.receiveOffsetRatio.y < 0.5) {
setRecievePosition("above");
} else {
setRecievePosition("below");
}
},
[]
);
return (
<Animated.View layout={Layout}>

View File

@@ -25,18 +25,18 @@ import {
} from "@notesnook/editor/dist/cjs/toolbar/tool-definitions";
import { ToolId } from "@notesnook/editor";
export const tools = getAllTools() as any;
export const tools = () => getAllTools();
export const presets: { [name: string]: ToolbarGroupDefinition[] } = {
default: getDefaultPresets().default as any,
minimal: getDefaultPresets().minimal as any,
custom: []
};
export function findToolById(id: keyof typeof tools): {
export function findToolById(id: keyof ReturnType<typeof tools> ): {
title: string;
icon: string;
} {
return tools[id];
return tools()[id];
}
export function getToolIcon(id: ToolId, color: string) {
@@ -51,13 +51,14 @@ export function getToolIcon(id: ToolId, color: string) {
export function getUngroupedTools(
toolDefinition: (string | string[])[][]
): string[] {
const keys = Object.keys(tools);
): string[] {
const allTools = tools();
const keys = Object.keys(allTools);
const ungrouped = [];
const toolString = JSON.stringify(toolDefinition);
for (const key of keys) {
if (tools[key as ToolId].conditional) continue;
if ((allTools[key as ToolId] as any).conditional) continue;
if (!toolString.includes(key)) ungrouped.push(key);
}

View File

@@ -21,7 +21,7 @@ import { ToolbarDefinition, ToolDefinition } from "./types.js";
import { ToolId } from "./tools/index.js";
import { strings } from "@notesnook/intl";
const tools: Record<ToolId, ToolDefinition> = {
const tools = (): Record<ToolId, ToolDefinition> => ({
none: {
icon: "none",
title: ""
@@ -356,14 +356,14 @@ const tools: Record<ToolId, ToolDefinition> = {
title: strings.exportCsv(),
conditional: true
}
};
});
export function getToolDefinition(id: ToolId) {
return tools[id];
return tools()[id];
}
export function getAllTools() {
return tools;
return tools();
}
export function getDefaultPresets() {

View File

@@ -6555,6 +6555,10 @@ msgstr "This error usually means the database file is either corrupt or it could
msgid "This error usually means the search index is corrupted."
msgstr "This error usually means the search index is corrupted."
#: src/strings.ts:2643
msgid "This feature is not available on this plan."
msgstr "This feature is not available on this plan."
#: src/strings.ts:1934
msgid "This image cannot be previewed"
msgstr "This image cannot be previewed"

View File

@@ -6514,6 +6514,10 @@ msgstr ""
msgid "This error usually means the search index is corrupted."
msgstr ""
#: src/strings.ts:2643
msgid "This feature is not available on this plan."
msgstr ""
#: src/strings.ts:1934
msgid "This image cannot be previewed"
msgstr ""

View File

@@ -2639,5 +2639,6 @@ Use this if changes from other devices are not appearing on this device. This wi
confirmationEmailSent: () => t`Confirmation email sent`,
back: () => t`Back`,
invalidRecoveryKey: () =>
t`Invalid recovery key. Make sure to input your account recovery key, not a 2FA recovery code.`
t`Invalid recovery key. Make sure to input your account recovery key, not a 2FA recovery code.`,
featureNotAvailable: () => t`This feature is not available on this plan.`
};