mirror of
https://github.com/streetwriters/notesnook.git
synced 2025-12-23 15:09:33 +01:00
Merge branch 'master' into fix-localization-error
Signed-off-by: Ammar Ahmed <40239442+ammarahm-ed@users.noreply.github.com>
This commit is contained in:
@@ -10,7 +10,7 @@ const authors = readFileSync("AUTHORS", "utf-8");
|
||||
const isAuthor = authors.includes(`<${authorEmail}>`);
|
||||
|
||||
const SCOPES = [
|
||||
// for full list of scopes + details see: https://github.com/streetwriters/notesnook-private/blob/master/CONTRIBUTING.md#commit-guidelines
|
||||
// for full list of scopes + details see: https://github.com/streetwriters/notesnook/blob/master/CONTRIBUTING.md#commit-guidelines
|
||||
|
||||
"mobile",
|
||||
"web",
|
||||
@@ -36,7 +36,8 @@ const SCOPES = [
|
||||
"global",
|
||||
"docs",
|
||||
"themebuilder",
|
||||
"intl"
|
||||
"intl",
|
||||
"webclipper"
|
||||
];
|
||||
|
||||
module.exports = {
|
||||
|
||||
@@ -231,6 +231,23 @@ test("add tags to note", async ({ page }) => {
|
||||
expect(noteTags.every((t, i) => t === tags[i])).toBe(true);
|
||||
});
|
||||
|
||||
test("add tags to locked note", async ({ page }) => {
|
||||
const tags = ["incognito", "secret-stuff"];
|
||||
const app = new AppModel(page);
|
||||
await app.goto();
|
||||
const notes = await app.goToNotes();
|
||||
const note = await notes.createNote(NOTE);
|
||||
await note?.contextMenu.lock(PASSWORD);
|
||||
await note?.openLockedNote(PASSWORD);
|
||||
|
||||
await notes.editor.setTags(tags);
|
||||
await page.waitForTimeout(200);
|
||||
|
||||
const noteTags = await notes.editor.getTags();
|
||||
expect(noteTags).toHaveLength(tags.length);
|
||||
expect(noteTags.every((t, i) => t === tags[i])).toBe(true);
|
||||
});
|
||||
|
||||
for (const format of ["html", "txt", "md"] as const) {
|
||||
test(`export note as ${format}`, async ({ page }) => {
|
||||
const app = new AppModel(page);
|
||||
|
||||
@@ -66,7 +66,7 @@ class Vault {
|
||||
subtitle: strings.deleteVaultDesc(),
|
||||
inputs: {
|
||||
password: {
|
||||
label: strings.password(),
|
||||
label: strings.accountPassword(),
|
||||
autoComplete: "current-password"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -834,6 +834,7 @@ function UnlockNoteView(props: UnlockNoteViewProps) {
|
||||
if (!note || !note.content)
|
||||
throw new Error("note with this id does not exist.");
|
||||
|
||||
const tags = await db.notes.tags(note.id);
|
||||
useEditorStore.getState().addSession({
|
||||
type: session.note.readonly ? "readonly" : "default",
|
||||
locked: true,
|
||||
@@ -841,6 +842,7 @@ function UnlockNoteView(props: UnlockNoteViewProps) {
|
||||
note: session.note,
|
||||
saveState: SaveState.Saved,
|
||||
sessionId: `${Date.now()}`,
|
||||
tags,
|
||||
pinned: session.pinned,
|
||||
preview: session.preview,
|
||||
content: note.content
|
||||
|
||||
@@ -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}
|
||||
@@ -460,7 +456,7 @@ function TiptapWrapper(
|
||||
: EDITOR_ZOOM.STEP;
|
||||
const zoom = Math.min(
|
||||
EDITOR_ZOOM.MAX,
|
||||
Math.max(EDITOR_ZOOM.MIN, editorConfig.zoom + delta)
|
||||
Math.max(EDITOR_ZOOM.MIN, Math.round(editorConfig.zoom + delta))
|
||||
);
|
||||
setEditorConfig({ zoom });
|
||||
}
|
||||
|
||||
@@ -127,12 +127,19 @@ export const IssueDialog = DialogManager.register(function IssueDialog(
|
||||
<Link
|
||||
href="https://github.com/streetwriters/notesnook/issues"
|
||||
title="github.com/streetwriters/notesnook/issues"
|
||||
/>{" "}
|
||||
target="_blank"
|
||||
>
|
||||
github.com/streetwriters/notesnook/issues
|
||||
</Link>
|
||||
{strings.issueNotice[1]()}{" "}
|
||||
<Link
|
||||
href="https://discord.gg/zQBK97EE22"
|
||||
title={strings.issueNotice[2]()}
|
||||
/>
|
||||
target="_blank"
|
||||
>
|
||||
{strings.issueNotice[2]()}
|
||||
</Link>
|
||||
/
|
||||
</Text>
|
||||
<Text variant="subBody" mt={1}>
|
||||
{getDeviceInfo([`Pro: ${isUserPremium()}`])
|
||||
|
||||
@@ -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)}
|
||||
|
||||
@@ -166,7 +166,11 @@ export function Importer() {
|
||||
<Box as="ol" sx={{ my: 1 }}>
|
||||
<Text as="li" variant="body">
|
||||
Go to{" "}
|
||||
<Link href="https://importer.notesnook.com/" target="_blank">
|
||||
<Link
|
||||
href="https://importer.notesnook.com/"
|
||||
target="_blank"
|
||||
sx={{ color: "accent" }}
|
||||
>
|
||||
https://importer.notesnook.com/
|
||||
</Link>
|
||||
</Text>
|
||||
|
||||
@@ -421,7 +421,7 @@ class EditorStore extends BaseStore<EditorStore> {
|
||||
continue;
|
||||
|
||||
updateSession(session.id, undefined, {
|
||||
tags: await getTags(session.note.id)
|
||||
tags: await db.notes.tags(session.note.id)
|
||||
});
|
||||
}
|
||||
} else if (
|
||||
@@ -432,7 +432,7 @@ class EditorStore extends BaseStore<EditorStore> {
|
||||
event.item.toType === "note"
|
||||
) {
|
||||
updateSession(event.item.toId, undefined, {
|
||||
tags: await getTags(event.item.toId)
|
||||
tags: await db.notes.tags(event.item.toId)
|
||||
});
|
||||
}
|
||||
} else if (event.collection === "tags") {
|
||||
@@ -445,7 +445,7 @@ class EditorStore extends BaseStore<EditorStore> {
|
||||
continue;
|
||||
console.log("UDPATE");
|
||||
updateSession(session.id, undefined, {
|
||||
tags: await getTags(session.note.id)
|
||||
tags: await db.notes.tags(session.note.id)
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -671,7 +671,7 @@ class EditorStore extends BaseStore<EditorStore> {
|
||||
const attachmentsLength = await db.attachments
|
||||
.ofNote(note.id, "all")
|
||||
.count();
|
||||
const tags = await getTags(note.id);
|
||||
const tags = await db.notes.tags(note.id);
|
||||
const colors = await db.relations.to(note, "color").get();
|
||||
if (note.readonly) {
|
||||
this.addSession(
|
||||
@@ -871,12 +871,19 @@ class EditorStore extends BaseStore<EditorStore> {
|
||||
};
|
||||
|
||||
newSession = () => {
|
||||
const state = useEditorStore.getState();
|
||||
const session = state.sessions.find((session) => session.type === "new");
|
||||
if (session) {
|
||||
session.context = useNoteStore.getState().context;
|
||||
this.activateSession(session.id);
|
||||
} else {
|
||||
this.addSession({
|
||||
type: "new",
|
||||
id: getId(),
|
||||
context: useNoteStore.getState().context,
|
||||
saveState: SaveState.NotSaved
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
closeSessions = (...ids: string[]) => {
|
||||
@@ -1020,12 +1027,3 @@ async function waitForSync() {
|
||||
db.eventManager.subscribe(EVENTS.syncCompleted, resolve, true);
|
||||
});
|
||||
}
|
||||
|
||||
async function getTags(noteId: string) {
|
||||
return await db.relations
|
||||
.to({ id: noteId, type: "note" }, "tag")
|
||||
.selector.items(undefined, {
|
||||
sortBy: "dateCreated",
|
||||
sortDirection: "asc"
|
||||
});
|
||||
}
|
||||
|
||||
@@ -19,6 +19,22 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import snarkdown from "snarkdown";
|
||||
|
||||
export function mdToHtml(markdown: string) {
|
||||
return snarkdown(markdown);
|
||||
function addAttributes(
|
||||
html: string,
|
||||
tag: keyof HTMLElementTagNameMap,
|
||||
attributes: Record<string, string>
|
||||
) {
|
||||
const temp = document.createElement("div");
|
||||
temp.innerHTML = html;
|
||||
const elements = temp.querySelectorAll(tag);
|
||||
elements.forEach((element) => {
|
||||
Object.entries(attributes).forEach(([key, value]) => {
|
||||
element.setAttribute(key, value);
|
||||
});
|
||||
});
|
||||
return temp.innerHTML;
|
||||
}
|
||||
|
||||
export function mdToHtml(markdown: string) {
|
||||
return addAttributes(snarkdown(markdown), "a", { target: "_blank" });
|
||||
}
|
||||
|
||||
@@ -985,7 +985,14 @@ export function AuthField(props: FieldProps) {
|
||||
p: "12px",
|
||||
borderRadius: "default",
|
||||
bg: "background",
|
||||
boxShadow: "0px 0px 5px 0px #00000019"
|
||||
boxShadow: "0px 0px 5px 0px #00000019",
|
||||
"::-moz-appearance": "textfield",
|
||||
"::-webkit-inner-spin-button": {
|
||||
"-webkit-appearance": "none"
|
||||
},
|
||||
"::-webkit-outer-spin-button": {
|
||||
"-webkit-appearance": "none"
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
||||
30
extensions/web-clipper/README.md
Normal file
30
extensions/web-clipper/README.md
Normal file
@@ -0,0 +1,30 @@
|
||||
# Contributing guidelines
|
||||
|
||||
Please read the [contributing guidelines](../../CONTRIBUTING.md) beforehand.
|
||||
|
||||
### Setting web clipper locally
|
||||
|
||||
#### Running the web clipper
|
||||
|
||||
1. Install packages and setup the repo. Run this command in the repository root:
|
||||
```sh
|
||||
npm install
|
||||
```
|
||||
1. Run the Notesnook webapp:
|
||||
```sh
|
||||
npm run start:web
|
||||
```
|
||||
1. Navigate to the web clipper folder:
|
||||
```sh
|
||||
cd extensions/web-clipper
|
||||
```
|
||||
1. Run the web clipper:
|
||||
```sh
|
||||
npm run dev:chrome
|
||||
```
|
||||
|
||||
#### Viewing the web clipper
|
||||
|
||||
1. Open chrome and go to `chrome://extensions`.
|
||||
1. Turn on "Developer Mode".
|
||||
1. Click on "Load unpacked" and select the `extensions/web-clipper/build` folder.
|
||||
@@ -75,6 +75,8 @@ function attachMessagePort() {
|
||||
height: document.body.clientHeight,
|
||||
width: document.body.clientWidth
|
||||
};
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -197,6 +197,15 @@ test("update note", () =>
|
||||
expect(note?.favorite).toBe(true);
|
||||
}));
|
||||
|
||||
test("get note tags", () =>
|
||||
noteTest({
|
||||
...TEST_NOTE
|
||||
}).then(async ({ db, id }) => {
|
||||
const tag = await db.tags.add({ title: "hello" });
|
||||
await db.relations.add({ type: "tag", id: tag }, { type: "note", id });
|
||||
expect(await db.notes.tags(id)).toEqual([await db.tags.tag(tag)]);
|
||||
}));
|
||||
|
||||
test("get favorite notes", () =>
|
||||
noteTest({
|
||||
...TEST_NOTE,
|
||||
|
||||
@@ -176,6 +176,15 @@ export class Notes implements ICollection {
|
||||
return note;
|
||||
}
|
||||
|
||||
async tags(id: string) {
|
||||
return this.db.relations
|
||||
.to({ id, type: "note" }, "tag")
|
||||
.selector.items(undefined, {
|
||||
sortBy: "dateCreated",
|
||||
sortDirection: "asc"
|
||||
});
|
||||
}
|
||||
|
||||
// note(idOrNote: string | Note) {
|
||||
// if (!idOrNote) return;
|
||||
// const note =
|
||||
|
||||
50
packages/core/src/utils/__tests__/internal-link.test.ts
Normal file
50
packages/core/src/utils/__tests__/internal-link.test.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
This file is part of the Notesnook project (https://notesnook.com/)
|
||||
|
||||
Copyright (C) 2023 Streetwriters (Private) Limited
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
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 { describe, expect, it } from "vitest";
|
||||
import { parseInternalLink } from "../internal-link";
|
||||
|
||||
describe("parseInternalLink", () => {
|
||||
const invalidInternalLinks = [
|
||||
"",
|
||||
"invalid-url",
|
||||
"http://google.com",
|
||||
"https://google.com"
|
||||
];
|
||||
invalidInternalLinks.forEach((url) => {
|
||||
it(`should return undefined when not internal link: ${url}`, () => {
|
||||
expect(parseInternalLink(url)).toBeUndefined();
|
||||
});
|
||||
});
|
||||
const validInternalLinks = [
|
||||
{
|
||||
url: "nn://note/123",
|
||||
expected: { type: "note", id: "123", params: {} }
|
||||
},
|
||||
{
|
||||
url: "nn://note/123?blockId=456",
|
||||
expected: { type: "note", id: "123", params: { blockId: "456" } }
|
||||
}
|
||||
];
|
||||
validInternalLinks.forEach(({ url, expected }) => {
|
||||
it(`should parse internal link: ${url}`, () => {
|
||||
expect(parseInternalLink(url)).toEqual(expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -52,7 +52,12 @@ export function createInternalLink<T extends InternalLinkType>(
|
||||
}
|
||||
|
||||
export function parseInternalLink(link: string): InternalLink | undefined {
|
||||
const url = new URL(link);
|
||||
let url;
|
||||
try {
|
||||
url = new URL(link);
|
||||
} catch (e) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (url.protocol !== "nn:") return;
|
||||
const [type, id] = url.href.split("?")[0].split("/").slice(2);
|
||||
|
||||
@@ -19,8 +19,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
const MACHINE_ID = Math.floor(Math.random() * 0xffffff);
|
||||
const pid = Math.floor(Math.random() * 100000) % 0xffff;
|
||||
const PROCESS_UNIQUE = MACHINE_ID.toString(16).padStart(6, "0") + pid.toString(16).padStart(4, "0");
|
||||
let index = Math.floor(Math.random() * 0xffffff);
|
||||
const PROCESS_UNIQUE = MACHINE_ID.toString(16) + pid.toString(16);
|
||||
|
||||
export function createObjectId(date = Date.now()): string {
|
||||
index++;
|
||||
const time = Math.floor(date / 1000);
|
||||
|
||||
21
packages/editor/src/extensions/inline-code/index.ts
Normal file
21
packages/editor/src/extensions/inline-code/index.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
This file is part of the Notesnook project (https://notesnook.com/)
|
||||
|
||||
Copyright (C) 2023 Streetwriters (Private) Limited
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
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 { InlineCode } from "./inline-code";
|
||||
|
||||
export default InlineCode;
|
||||
22
packages/editor/src/extensions/inline-code/inline-code.ts
Normal file
22
packages/editor/src/extensions/inline-code/inline-code.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import Code from "@tiptap/extension-code";
|
||||
|
||||
export const InlineCode = Code.extend({
|
||||
excludes: "link",
|
||||
addAttributes() {
|
||||
return {
|
||||
...this.parent?.(),
|
||||
spellcheck: {
|
||||
default: "false",
|
||||
parseHTML: (element) => element.getAttribute("spellcheck"),
|
||||
renderHTML: (attributes) => {
|
||||
if (!attributes.spellcheck) {
|
||||
return {};
|
||||
}
|
||||
return {
|
||||
spellcheck: attributes.spellcheck
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
This file is part of the Notesnook project (https://notesnook.com/)
|
||||
|
||||
Copyright (C) 2023 Streetwriters (Private) Limited
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
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 { expect, test } from "vitest";
|
||||
import { createEditor, h } from "../../../../test-utils/index.js";
|
||||
import { Link } from "../../link/link.js";
|
||||
import { InlineCode } from "../inline-code";
|
||||
|
||||
test("inline code has spellcheck disabled", async () => {
|
||||
const el = h("code", ["blazingly fast javascript"]);
|
||||
const editor = createEditor({
|
||||
initialContent: el.outerHTML,
|
||||
extensions: {
|
||||
link: Link,
|
||||
code: InlineCode
|
||||
}
|
||||
});
|
||||
expect(
|
||||
editor.editor.view.dom.querySelector("code")?.getAttribute("spellcheck")
|
||||
).toBe("false");
|
||||
});
|
||||
@@ -23,7 +23,6 @@ import {
|
||||
getHTMLFromFragment
|
||||
} from "@tiptap/core";
|
||||
import CharacterCount from "@tiptap/extension-character-count";
|
||||
import { Code } from "@tiptap/extension-code";
|
||||
import Color from "@tiptap/extension-color";
|
||||
import HorizontalRule from "@tiptap/extension-horizontal-rule";
|
||||
import { Link, LinkAttributes } from "./extensions/link/index.js";
|
||||
@@ -85,6 +84,7 @@ import { useEditorSearchStore } from "./toolbar/stores/search-store.js";
|
||||
import { DiffHighlighter } from "./extensions/diff-highlighter/index.js";
|
||||
import { getChangedNodes } from "./utils/prosemirror.js";
|
||||
import { strings } from "@notesnook/intl";
|
||||
import { InlineCode } from "./extensions/inline-code/inline-code.js";
|
||||
|
||||
interface TiptapStorage {
|
||||
dateFormat?: DateTimeOptions["dateFormat"];
|
||||
@@ -302,7 +302,7 @@ const useTiptap = (
|
||||
OutlineListItem,
|
||||
OutlineList.configure({ keepAttributes: true, keepMarks: true }),
|
||||
ListItem,
|
||||
Code.extend({ excludes: "link" }),
|
||||
InlineCode,
|
||||
Codemark,
|
||||
MathInline,
|
||||
MathBlock,
|
||||
|
||||
@@ -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,11 +90,20 @@ export function Toolbar(props: ToolbarProps) {
|
||||
|
||||
return (
|
||||
<>
|
||||
<ScrollContainer
|
||||
className="tabsScroll"
|
||||
suppressScrollY
|
||||
style={{ flex: 1 }}
|
||||
trackStyle={() => ({
|
||||
backgroundColor: "transparent",
|
||||
pointerEvents: "none"
|
||||
})}
|
||||
thumbStyle={() => ({ height: 3 })}
|
||||
>
|
||||
<Flex
|
||||
className={["editor-toolbar", className].join(" ")}
|
||||
sx={{
|
||||
flexWrap: isMobile ? "nowrap" : "wrap",
|
||||
overflowX: isMobile ? "auto" : "hidden",
|
||||
bg: "background",
|
||||
borderRadius: isMobile ? "0px" : "default",
|
||||
...sx
|
||||
@@ -117,6 +127,7 @@ export function Toolbar(props: ToolbarProps) {
|
||||
})}
|
||||
</Flex>
|
||||
<EditorFloatingMenus editor={editor} />
|
||||
</ScrollContainer>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -107,6 +107,7 @@ export function EditLink(props: ToolProps) {
|
||||
const link = node ? findMark(node, LinkNode.name) : null;
|
||||
const attrs = link?.attrs || getMarkAttributes(editor.state, LinkNode.name);
|
||||
|
||||
if (!editor.isEditable) return null;
|
||||
if (attrs && isInternalLink(attrs.href))
|
||||
return (
|
||||
<ToolButton
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1692,18 +1692,20 @@ 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]
|
||||
}.`,
|
||||
transactionStatusToText: (
|
||||
key: keyof typeof TRANSACTION_STATUS | ({} & string)
|
||||
) => {
|
||||
return key in TRANSACTION_STATUS
|
||||
? TRANSACTION_STATUS[key as keyof typeof TRANSACTION_STATUS]()
|
||||
: key;
|
||||
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`,
|
||||
partially_refunded: () => t`Partially refunded`,
|
||||
disputed: () => t`Disputed`
|
||||
},
|
||||
viewReceipt: () => t`View receipt`,
|
||||
customDictWords: (count: number) =>
|
||||
|
||||
Reference in New Issue
Block a user