mirror of
https://github.com/streetwriters/notesnook.git
synced 2026-08-30 02:29:18 +02:00
Compare commits
8 Commits
fix/undo-r
...
fix-locali
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
83083134e9 | ||
|
|
61b8960aa5 | ||
|
|
5229408156 | ||
|
|
8e90adccab | ||
|
|
c19635b16a | ||
|
|
94e4b00294 | ||
|
|
76908b1fbd | ||
|
|
7409c5382d |
@@ -460,7 +460,7 @@ export class VaultDialog extends Component {
|
||||
async _deleteNote() {
|
||||
try {
|
||||
await db.vault.remove(this.state.note.id, this.password);
|
||||
await deleteItems([this.state.note.id], "note");
|
||||
await deleteItems("note", [this.state.note.id]);
|
||||
this.close();
|
||||
} catch (e) {
|
||||
this._takeErrorAction(e);
|
||||
|
||||
@@ -57,10 +57,7 @@ export const SectionHeader = React.memo<
|
||||
}: SectionHeaderProps) {
|
||||
const { colors } = useThemeColors();
|
||||
const { fontScale } = useWindowDimensions();
|
||||
const groupBy =
|
||||
strings.groupByStrings[
|
||||
groupOptions.groupBy as keyof typeof strings.groupByStrings
|
||||
]?.();
|
||||
const groupBy = strings.groupByStrings[groupOptions.groupBy]();
|
||||
const isCompactModeEnabled = useIsCompactModeEnabled(
|
||||
dataType as "note" | "notebook"
|
||||
);
|
||||
|
||||
@@ -18,6 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Item, ItemType, VirtualizedGrouping } from "@notesnook/core";
|
||||
import { strings } from "@notesnook/intl";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import React, { useEffect, useRef } from "react";
|
||||
import {
|
||||
@@ -46,7 +47,6 @@ import { MoveNotebookSheet } from "../sheets/move-notebook";
|
||||
import { Button } from "../ui/button";
|
||||
import { IconButton } from "../ui/icon-button";
|
||||
import Heading from "../ui/typography/heading";
|
||||
import { strings } from "@notesnook/intl";
|
||||
|
||||
export const SelectionHeader = React.memo(
|
||||
({
|
||||
@@ -103,12 +103,11 @@ export const SelectionHeader = React.memo(
|
||||
const deleteItem = async () => {
|
||||
if (!type) return;
|
||||
presentDialog({
|
||||
title: strings.doActions.delete[
|
||||
type as keyof typeof strings.doActions.delete
|
||||
](selectedItemsList.length),
|
||||
paragraph: strings.actionConfirmations.delete[
|
||||
type as keyof typeof strings.doActions.delete
|
||||
](selectedItemsList.length),
|
||||
title: strings.doActions.delete.unknown(type, selectedItemsList.length),
|
||||
paragraph: strings.actionConfirmations.delete.unknown(
|
||||
type,
|
||||
selectedItemsList.length
|
||||
),
|
||||
positiveText: strings.delete(),
|
||||
negativeText: strings.cancel(),
|
||||
positivePress: async () => {
|
||||
@@ -297,15 +296,35 @@ export const SelectionHeader = React.memo(
|
||||
{
|
||||
title: strings.moveToTrash(),
|
||||
onPress: async () => {
|
||||
deleteItems(
|
||||
undefined,
|
||||
useSelectionStore.getState().selectionMode
|
||||
).then(() => {
|
||||
useSelectionStore.getState().clearSelection();
|
||||
useSelectionStore.getState().setSelectionMode(undefined);
|
||||
});
|
||||
const selection = useSelectionStore.getState();
|
||||
if (!selection.selectionMode) return;
|
||||
await deleteItems(
|
||||
selection.selectionMode as ItemType,
|
||||
selection.selectedItemsList
|
||||
);
|
||||
selection.clearSelection();
|
||||
selection.setSelectionMode(undefined);
|
||||
},
|
||||
visible: type !== "trash",
|
||||
visible: type === "note" || type === "notebook",
|
||||
icon: "delete"
|
||||
},
|
||||
{
|
||||
title: strings.doActions.delete.unknown(
|
||||
type!,
|
||||
selectedItemsList.length
|
||||
),
|
||||
onPress: async () => {
|
||||
const selection = useSelectionStore.getState();
|
||||
if (!selection.selectionMode) return;
|
||||
await deleteItems(
|
||||
selection.selectionMode as ItemType,
|
||||
selection.selectedItemsList
|
||||
);
|
||||
selection.clearSelection();
|
||||
selection.setSelectionMode(undefined);
|
||||
},
|
||||
visible:
|
||||
type !== "trash" && type !== "note" && type !== "notebook",
|
||||
icon: "delete"
|
||||
},
|
||||
{
|
||||
|
||||
@@ -301,8 +301,8 @@ export const NotebookSheet = () => {
|
||||
}}
|
||||
onPress={async () => {
|
||||
await deleteItems(
|
||||
useItemSelectionStore.getState().getSelectedItemIds(),
|
||||
"notebook"
|
||||
"notebook",
|
||||
useItemSelectionStore.getState().getSelectedItemIds()
|
||||
);
|
||||
useSelectionStore.getState().clearSelection();
|
||||
useItemSelectionStore.setState({
|
||||
|
||||
@@ -557,7 +557,7 @@ export default function ReminderSheet({
|
||||
<Button
|
||||
key={mode}
|
||||
title={strings.reminderNotificationModes[
|
||||
mode as keyof typeof strings.reminderNotificationModes
|
||||
mode as keyof typeof ReminderNotificationModes
|
||||
]()}
|
||||
style={{
|
||||
marginRight: 12,
|
||||
|
||||
@@ -254,8 +254,8 @@ export const useActions = ({
|
||||
item.type === "color"
|
||||
) {
|
||||
presentDialog({
|
||||
title: strings.doActions.delete[item.type](1),
|
||||
paragraph: strings.actionConfirmations.delete[item.type](1),
|
||||
title: strings.doActions.delete.unknown(item.type, 1),
|
||||
paragraph: strings.actionConfirmations.delete.unknown(item.type, 1),
|
||||
positivePress: async () => {
|
||||
if (item.type === "reminder") {
|
||||
await db.reminders.remove(item.id);
|
||||
@@ -290,7 +290,7 @@ export const useActions = ({
|
||||
});
|
||||
} else {
|
||||
try {
|
||||
await deleteItems([item.id], item.type);
|
||||
await deleteItems(item.type, [item.id]);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
@@ -303,8 +303,8 @@ export const useActions = ({
|
||||
close();
|
||||
await sleep(300);
|
||||
presentDialog({
|
||||
title: strings.doActions.delete[item.itemType](1),
|
||||
paragraph: strings.actionConfirmations.delete[item.itemType](1),
|
||||
title: strings.doActions.delete.unknown(item.itemType, 1),
|
||||
paragraph: strings.actionConfirmations.delete.unknown(item.itemType, 1),
|
||||
positiveText: strings.delete(),
|
||||
negativeText: strings.cancel(),
|
||||
positivePress: async () => {
|
||||
@@ -313,10 +313,7 @@ export const useActions = ({
|
||||
Navigation.queueRoutesForUpdate();
|
||||
useSelectionStore.getState().setSelectionMode(undefined);
|
||||
ToastManager.show({
|
||||
heading:
|
||||
strings.actions.deleted[
|
||||
item.itemType as keyof typeof strings.actions.deleted
|
||||
](1),
|
||||
heading: strings.actions.deleted.unknown(item.itemType, 1),
|
||||
type: "success",
|
||||
context: "local"
|
||||
});
|
||||
@@ -957,11 +954,10 @@ export const useActions = ({
|
||||
id: "trash",
|
||||
title:
|
||||
item.type !== "notebook" && item.type !== "note"
|
||||
? strings.doActions.delete[
|
||||
item.type === "trash"
|
||||
? item.itemType
|
||||
: (item.type as keyof typeof strings.doActions.delete)
|
||||
](1)
|
||||
? strings.doActions.delete.unknown(
|
||||
item.type === "trash" ? item.itemType : item.type,
|
||||
1
|
||||
)
|
||||
: strings.moveToTrash(),
|
||||
icon: "delete-outline",
|
||||
type: "error",
|
||||
|
||||
@@ -39,11 +39,7 @@
|
||||
"@lingui/react": "4.11.2",
|
||||
"@lingui/core": "4.11.2",
|
||||
"react-native-check-version": "^1.3.0",
|
||||
"react-native-material-menu": "^2.0.0",
|
||||
"@trpc/client": "^10.45.2",
|
||||
"@trpc/react-query": "^10.45.2",
|
||||
"@trpc/server": "^10.45.2",
|
||||
"@tanstack/react-query": "^4.36.1"
|
||||
"react-native-material-menu": "^2.0.0"
|
||||
},
|
||||
"sideEffects": false
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ export const Home = ({ navigation, route }: NavigationProps<"Notes">) => {
|
||||
<SelectionHeader id={route.name} items={notes} type="note" />
|
||||
<Header
|
||||
renderedInRoute={route.name}
|
||||
title={strings.routes[route.name as keyof typeof strings.routes]()}
|
||||
title={strings.routes[route.name]()}
|
||||
canGoBack={false}
|
||||
hasSearch={true}
|
||||
onSearch={() => {
|
||||
@@ -72,9 +72,7 @@ export const Home = ({ navigation, route }: NavigationProps<"Notes">) => {
|
||||
dataType="note"
|
||||
renderedInRoute={route.name}
|
||||
loading={loading || !isFocused}
|
||||
headerTitle={strings.routes[
|
||||
route.name as keyof typeof strings.routes
|
||||
]?.()}
|
||||
headerTitle={strings.routes[route.name]()}
|
||||
placeholder={{
|
||||
title: route.name?.toLowerCase(),
|
||||
paragraph: strings.notesEmpty(),
|
||||
|
||||
@@ -196,8 +196,7 @@ const NotesPage = ({
|
||||
<Header
|
||||
renderedInRoute={route.name}
|
||||
title={
|
||||
title ||
|
||||
strings.routes[route.name as unknown as keyof typeof strings.routes]()
|
||||
route.name === "Monographs" ? strings.routes[route.name]() : title
|
||||
}
|
||||
canGoBack={params?.current?.canGoBack}
|
||||
hasSearch={true}
|
||||
|
||||
@@ -57,7 +57,7 @@ export const HomePicker = createSettingsPicker({
|
||||
});
|
||||
},
|
||||
formatValue: (item) => {
|
||||
return strings.routes[typeof item === "object" ? item.name : item]();
|
||||
return strings.routes[typeof item === "object" ? item.name : item]?.();
|
||||
},
|
||||
getItemKey: (item) => item.name,
|
||||
options: MenuItemsList.slice(0, MenuItemsList.length - 1),
|
||||
|
||||
@@ -32,7 +32,7 @@ export const STORE_LINK =
|
||||
|
||||
export const GROUP = {
|
||||
default: "default",
|
||||
None: "none",
|
||||
none: "none",
|
||||
abc: "abc",
|
||||
year: "year",
|
||||
week: "week",
|
||||
|
||||
@@ -17,6 +17,8 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { ItemType } from "@notesnook/core";
|
||||
import { strings } from "@notesnook/intl";
|
||||
import { Linking } from "react-native";
|
||||
import { db } from "../common/database";
|
||||
import { presentDialog } from "../components/dialog/functions";
|
||||
@@ -24,16 +26,18 @@ import { eSendEvent, ToastManager } from "../services/event-manager";
|
||||
import Navigation from "../services/navigation";
|
||||
import { useMenuStore } from "../stores/use-menu-store";
|
||||
import { useRelationStore } from "../stores/use-relation-store";
|
||||
import { useSelectionStore } from "../stores/use-selection-store";
|
||||
import { useTagStore } from "../stores/use-tag-store";
|
||||
import { eOnNotebookUpdated, eUpdateNoteInEditor } from "./events";
|
||||
import { getParentNotebookId } from "./notebooks";
|
||||
import { useTagStore } from "../stores/use-tag-store";
|
||||
import { strings } from "@notesnook/intl";
|
||||
|
||||
function confirmDeleteAllNotes(items, type, context) {
|
||||
return new Promise((resolve) => {
|
||||
function confirmDeleteAllNotes(
|
||||
items: string[],
|
||||
type: "notebook",
|
||||
context?: string
|
||||
) {
|
||||
return new Promise<{ delete: boolean; deleteNotes: boolean }>((resolve) => {
|
||||
presentDialog({
|
||||
title: strings.doActions.delete[type](items.length),
|
||||
title: strings.doActions.delete.notebook(items.length),
|
||||
positiveText: strings.delete(),
|
||||
negativeText: strings.cancel(),
|
||||
positivePress: (_inputValue, value) => {
|
||||
@@ -43,22 +47,21 @@ function confirmDeleteAllNotes(items, type, context) {
|
||||
},
|
||||
onClose: () => {
|
||||
setTimeout(() => {
|
||||
resolve({ delete: false });
|
||||
resolve({ delete: false, deleteNotes: false });
|
||||
});
|
||||
},
|
||||
context: context,
|
||||
check: {
|
||||
info: `Move all notes in ${
|
||||
items.length > 1 ? `these ${type}s` : `this ${type}`
|
||||
} to trash`,
|
||||
info: strings.deleteContainingNotes(items.length),
|
||||
type: "transparent"
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function deleteNotebook(id, deleteNotes) {
|
||||
async function deleteNotebook(id: string, deleteNotes: boolean) {
|
||||
const notebook = await db.notebooks.notebook(id);
|
||||
if (!notebook) return;
|
||||
const parentId = getParentNotebookId(id);
|
||||
if (deleteNotes) {
|
||||
const noteRelations = await db.relations.from(notebook, "note").get();
|
||||
@@ -74,14 +77,16 @@ async function deleteNotebook(id, deleteNotes) {
|
||||
}
|
||||
}
|
||||
|
||||
export const deleteItems = async (items, type, context) => {
|
||||
const ids = items ? items : useSelectionStore.getState().selectedItemsList;
|
||||
|
||||
export const deleteItems = async (
|
||||
type: ItemType,
|
||||
itemIds: string[],
|
||||
context?: string
|
||||
) => {
|
||||
if (type === "reminder") {
|
||||
await db.reminders.remove(...ids);
|
||||
await db.reminders.remove(...itemIds);
|
||||
useRelationStore.getState().update();
|
||||
} else if (type === "note") {
|
||||
for (const id of ids) {
|
||||
for (const id of itemIds) {
|
||||
if (db.monographs.isPublished(id)) {
|
||||
ToastManager.show({
|
||||
heading: strings.someNotesPublished(),
|
||||
@@ -104,20 +109,20 @@ export const deleteItems = async (items, type, context) => {
|
||||
);
|
||||
}
|
||||
} else if (type === "notebook") {
|
||||
const result = await confirmDeleteAllNotes(ids, "notebook", context);
|
||||
const result = await confirmDeleteAllNotes(itemIds, "notebook", context);
|
||||
if (!result.delete) return;
|
||||
for (const id of ids) {
|
||||
for (const id of itemIds) {
|
||||
await deleteNotebook(id, result.deleteNotes);
|
||||
eSendEvent(eOnNotebookUpdated, await getParentNotebookId(id));
|
||||
}
|
||||
} else if (type === "tag") {
|
||||
presentDialog({
|
||||
title: strings.doActions.delete.tag(ids.length),
|
||||
title: strings.doActions.delete.tag(itemIds.length),
|
||||
positiveText: strings.delete(),
|
||||
negativeText: strings.cancel(),
|
||||
paragraph: strings.actionConfirmations.delete.tag(2),
|
||||
positivePress: async () => {
|
||||
await db.tags.remove(...ids);
|
||||
await db.tags.remove(...itemIds);
|
||||
useTagStore.getState().refresh();
|
||||
useRelationStore.getState().update();
|
||||
},
|
||||
@@ -126,9 +131,9 @@ export const deleteItems = async (items, type, context) => {
|
||||
return;
|
||||
}
|
||||
|
||||
let deletedIds = [...ids];
|
||||
const deletedIds = [...itemIds];
|
||||
if (type === "notebook" || type === "note") {
|
||||
let message = strings.actions.movedToTrash[type](ids.length);
|
||||
const message = strings.actions.movedToTrash[type](itemIds.length);
|
||||
ToastManager.show({
|
||||
heading: message,
|
||||
type: "success",
|
||||
@@ -148,28 +153,21 @@ export const deleteItems = async (items, type, context) => {
|
||||
});
|
||||
} else {
|
||||
ToastManager.show({
|
||||
heading: strings.deleted(type, ids.length),
|
||||
heading: strings.actions.deleted.unknown(type, itemIds.length),
|
||||
type: "success"
|
||||
});
|
||||
}
|
||||
|
||||
Navigation.queueRoutesForUpdate();
|
||||
if (!items) {
|
||||
useSelectionStore.getState().clearSelection();
|
||||
}
|
||||
useMenuStore.getState().setColorNotes();
|
||||
if (type === "notebook") {
|
||||
ids.forEach(async (id) => {
|
||||
itemIds.forEach(async (id) => {
|
||||
eSendEvent(eOnNotebookUpdated, await getParentNotebookId(id));
|
||||
});
|
||||
useMenuStore.getState().setMenuPins();
|
||||
}
|
||||
};
|
||||
|
||||
export const openLinkInBrowser = async (link) => {
|
||||
try {
|
||||
Linking.openURL(link);
|
||||
} catch (error) {
|
||||
console.log(error.message);
|
||||
}
|
||||
export const openLinkInBrowser = async (link: string) => {
|
||||
Linking.openURL(link);
|
||||
};
|
||||
12
apps/mobile/package-lock.json
generated
12
apps/mobile/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@notesnook/mobile",
|
||||
"version": "3.0.22",
|
||||
"version": "3.0.23",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@notesnook/mobile",
|
||||
"version": "3.0.22",
|
||||
"version": "3.0.23",
|
||||
"hasInstallScript": true,
|
||||
"license": "GPL-3.0-or-later",
|
||||
"workspaces": [
|
||||
@@ -22,6 +22,10 @@
|
||||
"@notesnook/logger": "file:../../packages/logger",
|
||||
"@notesnook/theme": "file:../../packages/theme",
|
||||
"@notesnook/themes-server": "file:../../servers/themes",
|
||||
"@tanstack/react-query": "^4.36.1",
|
||||
"@trpc/client": "^10.45.2",
|
||||
"@trpc/react-query": "^10.45.2",
|
||||
"@trpc/server": "^10.45.2",
|
||||
"diffblazer": "^1.0.1",
|
||||
"react": "18.2.0",
|
||||
"react-native": "0.74.5"
|
||||
@@ -28895,10 +28899,6 @@
|
||||
"@readme/data-urls": "3.0.0",
|
||||
"@streetwriters/kysely": "^0.27.4",
|
||||
"@streetwriters/showdown": "^3.0.1-alpha.2",
|
||||
"@tanstack/react-query": "^4.36.1",
|
||||
"@trpc/client": "^10.45.2",
|
||||
"@trpc/react-query": "^10.45.2",
|
||||
"@trpc/server": "^10.45.2",
|
||||
"absolutify": "^0.1.0",
|
||||
"buffer": "^6.0.3",
|
||||
"dayjs": "^1.10.4",
|
||||
|
||||
@@ -45,6 +45,10 @@
|
||||
"@notesnook/themes-server": "file:../../servers/themes",
|
||||
"diffblazer": "^1.0.1",
|
||||
"react": "18.2.0",
|
||||
"react-native": "0.74.5"
|
||||
"react-native": "0.74.5",
|
||||
"@trpc/client": "^10.45.2",
|
||||
"@trpc/react-query": "^10.45.2",
|
||||
"@trpc/server": "^10.45.2",
|
||||
"@tanstack/react-query": "^4.36.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -369,11 +369,7 @@ function TipTap(props: TipTapProps) {
|
||||
<Toolbar
|
||||
editor={editor}
|
||||
location={"top"}
|
||||
sx={
|
||||
isTablet || isMobile
|
||||
? { overflowX: "scroll", flexWrap: "nowrap" }
|
||||
: {}
|
||||
}
|
||||
sx={isTablet || isMobile ? { flexWrap: "nowrap" } : {}}
|
||||
tools={toolbarConfig}
|
||||
defaultFontFamily={fontFamily}
|
||||
defaultFontSize={fontSize}
|
||||
|
||||
@@ -480,9 +480,7 @@ export const AddReminderDialog = DialogManager.register(
|
||||
checked={p.id === priority}
|
||||
onChange={() => setPriority(p.id)}
|
||||
/>
|
||||
{strings.reminderNotificationModes[
|
||||
p.title as keyof typeof strings.reminderNotificationModes
|
||||
]()}
|
||||
{strings.reminderNotificationModes(p.title)}
|
||||
</Label>
|
||||
))}
|
||||
</Flex>
|
||||
|
||||
@@ -258,7 +258,7 @@ function ChooseAuthenticator(props: ChooseAuthenticatorProps) {
|
||||
justifyContent: "start",
|
||||
alignItems: "start",
|
||||
textAlign: "left",
|
||||
bg: "transparent",
|
||||
bg: selected === index ? "shade" : "transparent",
|
||||
px: 0
|
||||
}}
|
||||
onClick={() => setSelected(index)}
|
||||
|
||||
@@ -130,7 +130,7 @@ export function BillingHistory() {
|
||||
{transaction.amount} {transaction.currency}
|
||||
</Text>
|
||||
<Text as="td" variant="body">
|
||||
{strings.transactionStatusToText[transaction.status]()}
|
||||
{strings.transactionStatusToText(transaction.status)}
|
||||
</Text>
|
||||
<Text as="td" variant="body">
|
||||
<Link
|
||||
|
||||
@@ -232,10 +232,31 @@ type RecoveryMethod = {
|
||||
isDangerous?: boolean;
|
||||
};
|
||||
|
||||
const recoveryMethods: RecoveryMethod[] = [
|
||||
{
|
||||
type: "key",
|
||||
testId: "step-recovery-key",
|
||||
title: () => strings.recoveryKeyMethod(),
|
||||
description: () => strings.recoveryKeyMethodDesc()
|
||||
},
|
||||
{
|
||||
type: "backup",
|
||||
testId: "step-backup",
|
||||
title: () => strings.backupFileMethod(),
|
||||
description: () => strings.backupFileMethodDesc()
|
||||
},
|
||||
{
|
||||
type: "reset",
|
||||
testId: "step-reset-account",
|
||||
title: () => strings.clearDataAndResetMethod(),
|
||||
description: () => strings.clearDataAndResetMethodDesc(),
|
||||
isDangerous: true
|
||||
}
|
||||
];
|
||||
|
||||
function RecoveryMethods(props: BaseRecoveryComponentProps<"methods">) {
|
||||
const { navigate } = props;
|
||||
const [selected, setSelected] = useState(0);
|
||||
const recoveryMethods = strings.accountRecoveryMethods as RecoveryMethod[];
|
||||
|
||||
if (isSessionExpired()) {
|
||||
navigate("new");
|
||||
|
||||
@@ -78,15 +78,7 @@ const Tiptap = ({
|
||||
const isFocusedRef = useRef<boolean>(false);
|
||||
const [undo, setUndo] = useState(false);
|
||||
const [redo, setRedo] = useState(false);
|
||||
const valueRef = useRef({
|
||||
undo,
|
||||
redo
|
||||
});
|
||||
tabRef.current = tab;
|
||||
valueRef.current = {
|
||||
undo,
|
||||
redo
|
||||
};
|
||||
|
||||
function restoreNoteSelection(state?: NoteState) {
|
||||
try {
|
||||
@@ -135,14 +127,8 @@ const Tiptap = ({
|
||||
editor as Editor,
|
||||
transaction.getMeta("ignoreEdit")
|
||||
);
|
||||
|
||||
if (valueRef.current.undo !== editor.can().undo()) {
|
||||
setUndo(editor.can().undo());
|
||||
}
|
||||
if (valueRef.current.redo !== editor.can().redo()) {
|
||||
setRedo(editor.can().redo());
|
||||
}
|
||||
},
|
||||
|
||||
openAttachmentPicker: (type) => {
|
||||
globalThis.editorControllers[tab.id]?.openFilePicker(type);
|
||||
return true;
|
||||
|
||||
@@ -34,6 +34,7 @@ import {
|
||||
useToolbarStore
|
||||
} from "./stores/toolbar-store.js";
|
||||
import { ToolbarDefinition } from "./types.js";
|
||||
import { ScrollContainer } from "@notesnook/ui";
|
||||
|
||||
type ToolbarProps = FlexProps & {
|
||||
editor: Editor;
|
||||
@@ -89,34 +90,44 @@ export function Toolbar(props: ToolbarProps) {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Flex
|
||||
className={["editor-toolbar", className].join(" ")}
|
||||
sx={{
|
||||
flexWrap: isMobile ? "nowrap" : "wrap",
|
||||
overflowX: isMobile ? "auto" : "hidden",
|
||||
bg: "background",
|
||||
borderRadius: isMobile ? "0px" : "default",
|
||||
...sx
|
||||
}}
|
||||
{...flexProps}
|
||||
>
|
||||
{toolbarTools.map((tools) => {
|
||||
return (
|
||||
<ToolbarGroup
|
||||
key={tools.join("")}
|
||||
tools={tools}
|
||||
editor={editor}
|
||||
groupId={tools.join("")}
|
||||
sx={{
|
||||
borderRight: "1px solid var(--separator)",
|
||||
":last-of-type": { borderRight: "none" },
|
||||
alignItems: "center"
|
||||
}}
|
||||
/>
|
||||
);
|
||||
<ScrollContainer
|
||||
className="tabsScroll"
|
||||
suppressScrollY
|
||||
style={{ flex: 1 }}
|
||||
trackStyle={() => ({
|
||||
backgroundColor: "transparent",
|
||||
pointerEvents: "none"
|
||||
})}
|
||||
</Flex>
|
||||
<EditorFloatingMenus editor={editor} />
|
||||
thumbStyle={() => ({ height: 3 })}
|
||||
>
|
||||
<Flex
|
||||
className={["editor-toolbar", className].join(" ")}
|
||||
sx={{
|
||||
flexWrap: isMobile ? "nowrap" : "wrap",
|
||||
bg: "background",
|
||||
borderRadius: isMobile ? "0px" : "default",
|
||||
...sx
|
||||
}}
|
||||
{...flexProps}
|
||||
>
|
||||
{toolbarTools.map((tools) => {
|
||||
return (
|
||||
<ToolbarGroup
|
||||
key={tools.join("")}
|
||||
tools={tools}
|
||||
editor={editor}
|
||||
groupId={tools.join("")}
|
||||
sx={{
|
||||
borderRight: "1px solid var(--separator)",
|
||||
":last-of-type": { borderRight: "none" },
|
||||
alignItems: "center"
|
||||
}}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</Flex>
|
||||
<EditorFloatingMenus editor={editor} />
|
||||
</ScrollContainer>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -73,7 +73,7 @@ const ACTIONS = [
|
||||
{
|
||||
action: "deleted",
|
||||
label: "deleted",
|
||||
dataTypes: ["attachment", "reminder", "tag", "note"]
|
||||
dataTypes: ["attachment", "reminder", "tag", "note", "notebook"]
|
||||
},
|
||||
{
|
||||
action: "movedToTrash",
|
||||
@@ -206,6 +206,28 @@ const DO_ACTIONS_TEMPLATE = (action, dataTypes) => `${action}: {
|
||||
${dataTypes}
|
||||
}`;
|
||||
|
||||
const UNKNOWN_DATA_TYPE_TEMPLATE = (
|
||||
exportName,
|
||||
action,
|
||||
types,
|
||||
fallbackSingularTemplate,
|
||||
fallbackPluralTemplate
|
||||
) => `unknown: (type: string, count: number) => {
|
||||
switch (type) {
|
||||
${types
|
||||
.map(
|
||||
(type) => `case "${type}":
|
||||
return ${exportName}.${action}.${type}(count);`
|
||||
)
|
||||
.join("\n")}
|
||||
default:
|
||||
return plural(count, {
|
||||
one: \`${fallbackSingularTemplate}\`,
|
||||
other: \`${fallbackPluralTemplate}\`
|
||||
});
|
||||
}
|
||||
}`;
|
||||
|
||||
const DATA_TYPES_TEMPLATE = (
|
||||
type,
|
||||
singularTemplate,
|
||||
@@ -219,7 +241,7 @@ const MODULE_TEMPLATE = (exportName, strings) =>
|
||||
`/* eslint-disable header/header */
|
||||
// THIS FILE IS GENERATED. DO NOT EDIT MANUALLY.
|
||||
|
||||
import { t, plural } from "@lingui/macro";
|
||||
import { plural } from "@lingui/macro";
|
||||
|
||||
export const ${exportName} = {
|
||||
${strings}
|
||||
@@ -227,54 +249,80 @@ export const ${exportName} = {
|
||||
`;
|
||||
|
||||
function generateDoActionsStrings() {
|
||||
return generateStrings(
|
||||
DO_ACTIONS,
|
||||
(action, type) => `${action} ${DATA_TYPES[type].singular}`,
|
||||
(action, type) => `${action} # ${DATA_TYPES[type].plural}`
|
||||
const exportName = "doActions";
|
||||
|
||||
return MODULE_TEMPLATE(
|
||||
exportName,
|
||||
generateStrings(
|
||||
exportName,
|
||||
DO_ACTIONS,
|
||||
(action, type) => `${action} ${DATA_TYPES[type].singular}`,
|
||||
(action, type) => `${action} # ${DATA_TYPES[type].plural}`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function generateActionsStrings() {
|
||||
return generateStrings(
|
||||
ACTIONS,
|
||||
(action, type) => `${DATA_TYPES[type].singularCamelCase} ${action}`,
|
||||
(action, type) => `# ${DATA_TYPES[type].plural} ${action}`
|
||||
const exportName = "actions";
|
||||
return MODULE_TEMPLATE(
|
||||
exportName,
|
||||
generateStrings(
|
||||
exportName,
|
||||
ACTIONS,
|
||||
(action, type) => `${DATA_TYPES[type].singularCamelCase} ${action}`,
|
||||
(action, type) => `# ${DATA_TYPES[type].plural} ${action}`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function generateActionConfirmationStrings() {
|
||||
return generateStrings(
|
||||
ACTION_CONFIRMATIONS,
|
||||
(action, type) =>
|
||||
`Are you sure you want to ${action} this ${DATA_TYPES[type].singular}?`,
|
||||
(action, type) =>
|
||||
`Are you sure you to ${action} these ${DATA_TYPES[type].plural}?`
|
||||
const exportName = "actionConfirmations";
|
||||
return MODULE_TEMPLATE(
|
||||
exportName,
|
||||
generateStrings(
|
||||
exportName,
|
||||
ACTION_CONFIRMATIONS,
|
||||
(action, type) =>
|
||||
`Are you sure you want to ${action} this ${DATA_TYPES[type].singular}?`,
|
||||
(action, type) =>
|
||||
`Are you sure you to ${action} these ${DATA_TYPES[type].plural}?`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function generateActionErrorStrings() {
|
||||
return generateStrings(
|
||||
ACTION_ERRORS,
|
||||
(action, type) =>
|
||||
`${DATA_TYPES[type].singularCamelCase} could not be ${action}`,
|
||||
(action, type) => `# ${DATA_TYPES[type].plural} could not be ${action}`
|
||||
const exportName = "actionErrors";
|
||||
return MODULE_TEMPLATE(
|
||||
exportName,
|
||||
generateStrings(
|
||||
exportName,
|
||||
ACTION_ERRORS,
|
||||
(action, type) =>
|
||||
`${DATA_TYPES[type].singularCamelCase} could not be ${action}`,
|
||||
(action, type) => `# ${DATA_TYPES[type].plural} could not be ${action}`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function generateInProgressActionsStrings() {
|
||||
return generateStrings(
|
||||
IN_PROGRESS_ACTIONS,
|
||||
(action, type) => `${action} ${DATA_TYPES[type].singular}...`,
|
||||
(action, type) => `${action} # ${DATA_TYPES[type].plural}...`
|
||||
const exportName = "inProgressActions";
|
||||
return MODULE_TEMPLATE(
|
||||
exportName,
|
||||
generateStrings(
|
||||
exportName,
|
||||
IN_PROGRESS_ACTIONS,
|
||||
(action, type) => `${action} ${DATA_TYPES[type].singular}...`,
|
||||
(action, type) => `${action} # ${DATA_TYPES[type].plural}...`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function generateStrings(actions, singular, plural) {
|
||||
function generateStrings(exportName, actions, singular, plural) {
|
||||
let result = [];
|
||||
for (const action of actions) {
|
||||
const subResults = [];
|
||||
const actionName = action.label;
|
||||
for (const type of action.dataTypes) {
|
||||
const actionName = action.label;
|
||||
subResults.push(
|
||||
DATA_TYPES_TEMPLATE(
|
||||
type,
|
||||
@@ -283,6 +331,15 @@ function generateStrings(actions, singular, plural) {
|
||||
)
|
||||
);
|
||||
}
|
||||
subResults.push(
|
||||
UNKNOWN_DATA_TYPE_TEMPLATE(
|
||||
exportName,
|
||||
action.action,
|
||||
action.dataTypes,
|
||||
singular(actionName, "item"),
|
||||
plural(actionName, "item")
|
||||
)
|
||||
);
|
||||
result.push(DO_ACTIONS_TEMPLATE(action.action, subResults.join(",\n")));
|
||||
}
|
||||
return result.join(",\n");
|
||||
@@ -290,27 +347,18 @@ function generateStrings(actions, singular, plural) {
|
||||
|
||||
mkdirSync("./generated/", { recursive: true });
|
||||
|
||||
writeFileSync(
|
||||
"./generated/do-actions.ts",
|
||||
MODULE_TEMPLATE("doActions", generateDoActionsStrings())
|
||||
);
|
||||
writeFileSync("./generated/do-actions.ts", generateDoActionsStrings());
|
||||
|
||||
writeFileSync(
|
||||
"./generated/actions.ts",
|
||||
MODULE_TEMPLATE("actions", generateActionsStrings())
|
||||
);
|
||||
writeFileSync("./generated/actions.ts", generateActionsStrings());
|
||||
|
||||
writeFileSync(
|
||||
"./generated/in-progress-actions.ts",
|
||||
MODULE_TEMPLATE("inProgressActions", generateInProgressActionsStrings())
|
||||
generateInProgressActionsStrings()
|
||||
);
|
||||
|
||||
writeFileSync(
|
||||
"./generated/action-errors.ts",
|
||||
MODULE_TEMPLATE("actionErrors", generateActionErrorStrings())
|
||||
);
|
||||
writeFileSync("./generated/action-errors.ts", generateActionErrorStrings());
|
||||
|
||||
writeFileSync(
|
||||
"./generated/action-confirmations.ts",
|
||||
MODULE_TEMPLATE("actionConfirmations", generateActionConfirmationStrings())
|
||||
generateActionConfirmationStrings()
|
||||
);
|
||||
|
||||
@@ -17,11 +17,11 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import { plural, select, t } from "@lingui/macro";
|
||||
import { doActions } from "../generated/do-actions";
|
||||
import { actions } from "../generated/actions";
|
||||
import { inProgressActions } from "../generated/in-progress-actions";
|
||||
import { actionErrors } from "../generated/action-errors";
|
||||
import { actionConfirmations } from "../generated/action-confirmations";
|
||||
import { actionErrors } from "../generated/action-errors";
|
||||
import { actions } from "../generated/actions";
|
||||
import { doActions } from "../generated/do-actions";
|
||||
import { inProgressActions } from "../generated/in-progress-actions";
|
||||
|
||||
const SEARCH_IN_ROUTE_STRINGS = {
|
||||
Notes: () => t`Search in in Notes`,
|
||||
@@ -38,6 +38,19 @@ const SEARCH_IN_ROUTE_STRINGS = {
|
||||
Monographs: () => t`Search in in Monographs`
|
||||
};
|
||||
|
||||
const TRANSACTION_STATUS = {
|
||||
completed: () => t`Completed`,
|
||||
refunded: () => t`"Refunded`,
|
||||
partially_refunded: () => t`Partially refunded`,
|
||||
disputed: () => t`Disputed`
|
||||
};
|
||||
|
||||
const REMINDER_NOTIFICATION_MODES = {
|
||||
Silent: () => t`Silent`,
|
||||
Vibrate: () => t`Vibrate`,
|
||||
Urgent: () => t`Urgent`
|
||||
};
|
||||
|
||||
export const strings = {
|
||||
done: () => t`Done`,
|
||||
verifyItsYou: () => t`Please verify it's you`,
|
||||
@@ -607,10 +620,14 @@ $headline$: Use starting line of the note as title.`,
|
||||
6: () => t`Sat`
|
||||
},
|
||||
selectDate: () => t`Select date`,
|
||||
reminderNotificationModes: {
|
||||
Silent: () => t`Silent`,
|
||||
Vibrate: () => t`Vibrate`,
|
||||
Urgent: () => t`Urgent`
|
||||
reminderNotificationModes: (
|
||||
mode: keyof typeof REMINDER_NOTIFICATION_MODES | ({} & string)
|
||||
) => {
|
||||
return mode in REMINDER_NOTIFICATION_MODES
|
||||
? REMINDER_NOTIFICATION_MODES[
|
||||
mode as keyof typeof REMINDER_NOTIFICATION_MODES
|
||||
]()
|
||||
: mode;
|
||||
},
|
||||
selectBackupsFolder: () => t`Select backups folder`,
|
||||
oldNew: () => t`Old - new`,
|
||||
@@ -630,7 +647,7 @@ $headline$: Use starting line of the note as title.`,
|
||||
},
|
||||
groupByStrings: {
|
||||
default: () => t`Default`,
|
||||
None: () => t`None`,
|
||||
none: () => t`None`,
|
||||
abc: () => t`Abc`,
|
||||
year: () => t`Year`,
|
||||
week: () => t`Week`,
|
||||
@@ -1482,7 +1499,7 @@ For example:
|
||||
return (
|
||||
SEARCH_IN_ROUTE_STRINGS[
|
||||
routeName as keyof typeof SEARCH_IN_ROUTE_STRINGS
|
||||
]() || t`Search in ${routeName}`
|
||||
]?.() || t`Search in ${routeName}`
|
||||
);
|
||||
},
|
||||
logoutConfirmation: () =>
|
||||
@@ -1675,12 +1692,15 @@ For example:
|
||||
t`Your account is now 100% secure against unauthorized logins.`,
|
||||
sms: () => t`phone number`,
|
||||
app: () => t`authentication app`,
|
||||
mfaFallbackMethodText: (fallback: string, primary: string) =>
|
||||
`You will now receive your 2FA codes on your ${
|
||||
strings[fallback as keyof typeof strings]
|
||||
} in case you lose access to your ${
|
||||
strings[primary as keyof typeof strings]
|
||||
}.`,
|
||||
mfaFallbackMethodText: (
|
||||
fallback: "app" | "sms" | "email",
|
||||
primary: "app" | "sms" | "email"
|
||||
) =>
|
||||
`You will now receive your 2FA codes on your ${strings[
|
||||
fallback
|
||||
]().toLocaleLowerCase()} in case you lose access to your ${strings[
|
||||
primary
|
||||
]().toLocaleLowerCase()}.`,
|
||||
transactionStatusToText: {
|
||||
completed: () => t`Completed`,
|
||||
refunded: () => t`"Refunded`,
|
||||
@@ -1737,32 +1757,18 @@ For example:
|
||||
notebooksAllCaps: () => t`NOTEBOOKS`,
|
||||
authenticatedAs: (email?: string) => t`Authenticated as ${email}`,
|
||||
rememberedYourPassword: () => t`Remembered your password?`,
|
||||
accountRecoveryMethods: [
|
||||
{
|
||||
type: "key",
|
||||
testId: "step-recovery-key",
|
||||
title: () => `Use recovery key`,
|
||||
description: () =>
|
||||
`Your data recovery key is basically a hashed version of your password (plus some random salt). It can be used to decrypt your data for re-encryption.`
|
||||
},
|
||||
{
|
||||
type: "backup",
|
||||
testId: "step-backup",
|
||||
title: () => `Use a backup file`,
|
||||
description: () =>
|
||||
`If you don't have a recovery key, you can recover your data by restoring a Notesnook data backup file (.nnbackup).`
|
||||
},
|
||||
{
|
||||
type: "reset",
|
||||
testId: "step-reset-account",
|
||||
title: () => `Clear data & reset account`,
|
||||
description: () =>
|
||||
`EXTREMELY DANGEROUS! This action is irreversible. All your data including notes, notebooks, attachments & settings will be deleted. This is a full account reset. Proceed with caution.`,
|
||||
isDangerous: true
|
||||
}
|
||||
],
|
||||
chooseRecoveryMethod: () => t`Choose a recovery method`,
|
||||
chooseRecoveryMethodDesc: () => t`How do you want to recover your account?`,
|
||||
recoveryKeyMethod: () => t`Use recovery key`,
|
||||
recoveryKeyMethodDesc: () =>
|
||||
t`Your data recovery key is basically a hashed version of your password (plus some random salt). It can be used to decrypt your data for re-encryption.`,
|
||||
backupFileMethod: () => t`Use a backup file`,
|
||||
backupFileMethodDesc: () =>
|
||||
t`If you don't have a recovery key, you can recover your data by restoring a Notesnook data backup file (.nnbackup).`,
|
||||
clearDataAndResetMethod: () => t`Clear data & reset account`,
|
||||
clearDataAndResetMethodDesc: () =>
|
||||
t`EXTREMELY DANGEROUS! This action is irreversible. All your data including notes, notebooks, attachments & settings will be deleted. This is a full account reset. Proceed with caution.`,
|
||||
|
||||
browse: () => t`Browse`,
|
||||
dontShowAgain: () => t`Don't show again`,
|
||||
dontShowAgainConfirm: () => t`Don't show again on this device?`,
|
||||
|
||||
Reference in New Issue
Block a user