mirror of
https://github.com/streetwriters/notesnook.git
synced 2025-12-22 22:49:45 +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 isAuthor = authors.includes(`<${authorEmail}>`);
|
||||||
|
|
||||||
const SCOPES = [
|
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",
|
"mobile",
|
||||||
"web",
|
"web",
|
||||||
@@ -36,7 +36,8 @@ const SCOPES = [
|
|||||||
"global",
|
"global",
|
||||||
"docs",
|
"docs",
|
||||||
"themebuilder",
|
"themebuilder",
|
||||||
"intl"
|
"intl",
|
||||||
|
"webclipper"
|
||||||
];
|
];
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
|||||||
@@ -231,6 +231,23 @@ test("add tags to note", async ({ page }) => {
|
|||||||
expect(noteTags.every((t, i) => t === tags[i])).toBe(true);
|
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) {
|
for (const format of ["html", "txt", "md"] as const) {
|
||||||
test(`export note as ${format}`, async ({ page }) => {
|
test(`export note as ${format}`, async ({ page }) => {
|
||||||
const app = new AppModel(page);
|
const app = new AppModel(page);
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ class Vault {
|
|||||||
subtitle: strings.deleteVaultDesc(),
|
subtitle: strings.deleteVaultDesc(),
|
||||||
inputs: {
|
inputs: {
|
||||||
password: {
|
password: {
|
||||||
label: strings.password(),
|
label: strings.accountPassword(),
|
||||||
autoComplete: "current-password"
|
autoComplete: "current-password"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -834,6 +834,7 @@ function UnlockNoteView(props: UnlockNoteViewProps) {
|
|||||||
if (!note || !note.content)
|
if (!note || !note.content)
|
||||||
throw new Error("note with this id does not exist.");
|
throw new Error("note with this id does not exist.");
|
||||||
|
|
||||||
|
const tags = await db.notes.tags(note.id);
|
||||||
useEditorStore.getState().addSession({
|
useEditorStore.getState().addSession({
|
||||||
type: session.note.readonly ? "readonly" : "default",
|
type: session.note.readonly ? "readonly" : "default",
|
||||||
locked: true,
|
locked: true,
|
||||||
@@ -841,6 +842,7 @@ function UnlockNoteView(props: UnlockNoteViewProps) {
|
|||||||
note: session.note,
|
note: session.note,
|
||||||
saveState: SaveState.Saved,
|
saveState: SaveState.Saved,
|
||||||
sessionId: `${Date.now()}`,
|
sessionId: `${Date.now()}`,
|
||||||
|
tags,
|
||||||
pinned: session.pinned,
|
pinned: session.pinned,
|
||||||
preview: session.preview,
|
preview: session.preview,
|
||||||
content: note.content
|
content: note.content
|
||||||
|
|||||||
@@ -369,11 +369,7 @@ function TipTap(props: TipTapProps) {
|
|||||||
<Toolbar
|
<Toolbar
|
||||||
editor={editor}
|
editor={editor}
|
||||||
location={"top"}
|
location={"top"}
|
||||||
sx={
|
sx={isTablet || isMobile ? { flexWrap: "nowrap" } : {}}
|
||||||
isTablet || isMobile
|
|
||||||
? { overflowX: "scroll", flexWrap: "nowrap" }
|
|
||||||
: {}
|
|
||||||
}
|
|
||||||
tools={toolbarConfig}
|
tools={toolbarConfig}
|
||||||
defaultFontFamily={fontFamily}
|
defaultFontFamily={fontFamily}
|
||||||
defaultFontSize={fontSize}
|
defaultFontSize={fontSize}
|
||||||
@@ -460,7 +456,7 @@ function TiptapWrapper(
|
|||||||
: EDITOR_ZOOM.STEP;
|
: EDITOR_ZOOM.STEP;
|
||||||
const zoom = Math.min(
|
const zoom = Math.min(
|
||||||
EDITOR_ZOOM.MAX,
|
EDITOR_ZOOM.MAX,
|
||||||
Math.max(EDITOR_ZOOM.MIN, editorConfig.zoom + delta)
|
Math.max(EDITOR_ZOOM.MIN, Math.round(editorConfig.zoom + delta))
|
||||||
);
|
);
|
||||||
setEditorConfig({ zoom });
|
setEditorConfig({ zoom });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -127,12 +127,19 @@ export const IssueDialog = DialogManager.register(function IssueDialog(
|
|||||||
<Link
|
<Link
|
||||||
href="https://github.com/streetwriters/notesnook/issues"
|
href="https://github.com/streetwriters/notesnook/issues"
|
||||||
title="github.com/streetwriters/notesnook/issues"
|
title="github.com/streetwriters/notesnook/issues"
|
||||||
/>{" "}
|
target="_blank"
|
||||||
|
>
|
||||||
|
github.com/streetwriters/notesnook/issues
|
||||||
|
</Link>
|
||||||
{strings.issueNotice[1]()}{" "}
|
{strings.issueNotice[1]()}{" "}
|
||||||
<Link
|
<Link
|
||||||
href="https://discord.gg/zQBK97EE22"
|
href="https://discord.gg/zQBK97EE22"
|
||||||
title={strings.issueNotice[2]()}
|
title={strings.issueNotice[2]()}
|
||||||
/>
|
target="_blank"
|
||||||
|
>
|
||||||
|
{strings.issueNotice[2]()}
|
||||||
|
</Link>
|
||||||
|
/
|
||||||
</Text>
|
</Text>
|
||||||
<Text variant="subBody" mt={1}>
|
<Text variant="subBody" mt={1}>
|
||||||
{getDeviceInfo([`Pro: ${isUserPremium()}`])
|
{getDeviceInfo([`Pro: ${isUserPremium()}`])
|
||||||
|
|||||||
@@ -258,7 +258,7 @@ function ChooseAuthenticator(props: ChooseAuthenticatorProps) {
|
|||||||
justifyContent: "start",
|
justifyContent: "start",
|
||||||
alignItems: "start",
|
alignItems: "start",
|
||||||
textAlign: "left",
|
textAlign: "left",
|
||||||
bg: "transparent",
|
bg: selected === index ? "shade" : "transparent",
|
||||||
px: 0
|
px: 0
|
||||||
}}
|
}}
|
||||||
onClick={() => setSelected(index)}
|
onClick={() => setSelected(index)}
|
||||||
|
|||||||
@@ -166,7 +166,11 @@ export function Importer() {
|
|||||||
<Box as="ol" sx={{ my: 1 }}>
|
<Box as="ol" sx={{ my: 1 }}>
|
||||||
<Text as="li" variant="body">
|
<Text as="li" variant="body">
|
||||||
Go to{" "}
|
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/
|
https://importer.notesnook.com/
|
||||||
</Link>
|
</Link>
|
||||||
</Text>
|
</Text>
|
||||||
|
|||||||
@@ -421,7 +421,7 @@ class EditorStore extends BaseStore<EditorStore> {
|
|||||||
continue;
|
continue;
|
||||||
|
|
||||||
updateSession(session.id, undefined, {
|
updateSession(session.id, undefined, {
|
||||||
tags: await getTags(session.note.id)
|
tags: await db.notes.tags(session.note.id)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else if (
|
} else if (
|
||||||
@@ -432,7 +432,7 @@ class EditorStore extends BaseStore<EditorStore> {
|
|||||||
event.item.toType === "note"
|
event.item.toType === "note"
|
||||||
) {
|
) {
|
||||||
updateSession(event.item.toId, undefined, {
|
updateSession(event.item.toId, undefined, {
|
||||||
tags: await getTags(event.item.toId)
|
tags: await db.notes.tags(event.item.toId)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else if (event.collection === "tags") {
|
} else if (event.collection === "tags") {
|
||||||
@@ -445,7 +445,7 @@ class EditorStore extends BaseStore<EditorStore> {
|
|||||||
continue;
|
continue;
|
||||||
console.log("UDPATE");
|
console.log("UDPATE");
|
||||||
updateSession(session.id, undefined, {
|
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
|
const attachmentsLength = await db.attachments
|
||||||
.ofNote(note.id, "all")
|
.ofNote(note.id, "all")
|
||||||
.count();
|
.count();
|
||||||
const tags = await getTags(note.id);
|
const tags = await db.notes.tags(note.id);
|
||||||
const colors = await db.relations.to(note, "color").get();
|
const colors = await db.relations.to(note, "color").get();
|
||||||
if (note.readonly) {
|
if (note.readonly) {
|
||||||
this.addSession(
|
this.addSession(
|
||||||
@@ -871,12 +871,19 @@ class EditorStore extends BaseStore<EditorStore> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
newSession = () => {
|
newSession = () => {
|
||||||
this.addSession({
|
const state = useEditorStore.getState();
|
||||||
type: "new",
|
const session = state.sessions.find((session) => session.type === "new");
|
||||||
id: getId(),
|
if (session) {
|
||||||
context: useNoteStore.getState().context,
|
session.context = useNoteStore.getState().context;
|
||||||
saveState: SaveState.NotSaved
|
this.activateSession(session.id);
|
||||||
});
|
} else {
|
||||||
|
this.addSession({
|
||||||
|
type: "new",
|
||||||
|
id: getId(),
|
||||||
|
context: useNoteStore.getState().context,
|
||||||
|
saveState: SaveState.NotSaved
|
||||||
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
closeSessions = (...ids: string[]) => {
|
closeSessions = (...ids: string[]) => {
|
||||||
@@ -1020,12 +1027,3 @@ async function waitForSync() {
|
|||||||
db.eventManager.subscribe(EVENTS.syncCompleted, resolve, true);
|
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";
|
import snarkdown from "snarkdown";
|
||||||
|
|
||||||
export function mdToHtml(markdown: string) {
|
function addAttributes(
|
||||||
return snarkdown(markdown);
|
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",
|
p: "12px",
|
||||||
borderRadius: "default",
|
borderRadius: "default",
|
||||||
bg: "background",
|
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,
|
height: document.body.clientHeight,
|
||||||
width: document.body.clientWidth
|
width: document.body.clientWidth
|
||||||
};
|
};
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -197,6 +197,15 @@ test("update note", () =>
|
|||||||
expect(note?.favorite).toBe(true);
|
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", () =>
|
test("get favorite notes", () =>
|
||||||
noteTest({
|
noteTest({
|
||||||
...TEST_NOTE,
|
...TEST_NOTE,
|
||||||
|
|||||||
@@ -176,6 +176,15 @@ export class Notes implements ICollection {
|
|||||||
return note;
|
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) {
|
// note(idOrNote: string | Note) {
|
||||||
// if (!idOrNote) return;
|
// if (!idOrNote) return;
|
||||||
// const note =
|
// 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 {
|
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;
|
if (url.protocol !== "nn:") return;
|
||||||
const [type, id] = url.href.split("?")[0].split("/").slice(2);
|
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 MACHINE_ID = Math.floor(Math.random() * 0xffffff);
|
||||||
const pid = Math.floor(Math.random() * 100000) % 0xffff;
|
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);
|
let index = Math.floor(Math.random() * 0xffffff);
|
||||||
const PROCESS_UNIQUE = MACHINE_ID.toString(16) + pid.toString(16);
|
|
||||||
export function createObjectId(date = Date.now()): string {
|
export function createObjectId(date = Date.now()): string {
|
||||||
index++;
|
index++;
|
||||||
const time = Math.floor(date / 1000);
|
const time = Math.floor(date / 1000);
|
||||||
@@ -40,4 +41,4 @@ function swap16(val: number) {
|
|||||||
|
|
||||||
export function getObjectIdTimestamp(id: string) {
|
export function getObjectIdTimestamp(id: string) {
|
||||||
return new Date(parseInt(id.substring(0, 8), 16) * 1000);
|
return new Date(parseInt(id.substring(0, 8), 16) * 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
|
getHTMLFromFragment
|
||||||
} from "@tiptap/core";
|
} from "@tiptap/core";
|
||||||
import CharacterCount from "@tiptap/extension-character-count";
|
import CharacterCount from "@tiptap/extension-character-count";
|
||||||
import { Code } from "@tiptap/extension-code";
|
|
||||||
import Color from "@tiptap/extension-color";
|
import Color from "@tiptap/extension-color";
|
||||||
import HorizontalRule from "@tiptap/extension-horizontal-rule";
|
import HorizontalRule from "@tiptap/extension-horizontal-rule";
|
||||||
import { Link, LinkAttributes } from "./extensions/link/index.js";
|
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 { DiffHighlighter } from "./extensions/diff-highlighter/index.js";
|
||||||
import { getChangedNodes } from "./utils/prosemirror.js";
|
import { getChangedNodes } from "./utils/prosemirror.js";
|
||||||
import { strings } from "@notesnook/intl";
|
import { strings } from "@notesnook/intl";
|
||||||
|
import { InlineCode } from "./extensions/inline-code/inline-code.js";
|
||||||
|
|
||||||
interface TiptapStorage {
|
interface TiptapStorage {
|
||||||
dateFormat?: DateTimeOptions["dateFormat"];
|
dateFormat?: DateTimeOptions["dateFormat"];
|
||||||
@@ -302,7 +302,7 @@ const useTiptap = (
|
|||||||
OutlineListItem,
|
OutlineListItem,
|
||||||
OutlineList.configure({ keepAttributes: true, keepMarks: true }),
|
OutlineList.configure({ keepAttributes: true, keepMarks: true }),
|
||||||
ListItem,
|
ListItem,
|
||||||
Code.extend({ excludes: "link" }),
|
InlineCode,
|
||||||
Codemark,
|
Codemark,
|
||||||
MathInline,
|
MathInline,
|
||||||
MathBlock,
|
MathBlock,
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ import {
|
|||||||
useToolbarStore
|
useToolbarStore
|
||||||
} from "./stores/toolbar-store.js";
|
} from "./stores/toolbar-store.js";
|
||||||
import { ToolbarDefinition } from "./types.js";
|
import { ToolbarDefinition } from "./types.js";
|
||||||
|
import { ScrollContainer } from "@notesnook/ui";
|
||||||
|
|
||||||
type ToolbarProps = FlexProps & {
|
type ToolbarProps = FlexProps & {
|
||||||
editor: Editor;
|
editor: Editor;
|
||||||
@@ -89,34 +90,44 @@ export function Toolbar(props: ToolbarProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Flex
|
<ScrollContainer
|
||||||
className={["editor-toolbar", className].join(" ")}
|
className="tabsScroll"
|
||||||
sx={{
|
suppressScrollY
|
||||||
flexWrap: isMobile ? "nowrap" : "wrap",
|
style={{ flex: 1 }}
|
||||||
overflowX: isMobile ? "auto" : "hidden",
|
trackStyle={() => ({
|
||||||
bg: "background",
|
backgroundColor: "transparent",
|
||||||
borderRadius: isMobile ? "0px" : "default",
|
pointerEvents: "none"
|
||||||
...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>
|
thumbStyle={() => ({ height: 3 })}
|
||||||
<EditorFloatingMenus editor={editor} />
|
>
|
||||||
|
<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>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -107,6 +107,7 @@ export function EditLink(props: ToolProps) {
|
|||||||
const link = node ? findMark(node, LinkNode.name) : null;
|
const link = node ? findMark(node, LinkNode.name) : null;
|
||||||
const attrs = link?.attrs || getMarkAttributes(editor.state, LinkNode.name);
|
const attrs = link?.attrs || getMarkAttributes(editor.state, LinkNode.name);
|
||||||
|
|
||||||
|
if (!editor.isEditable) return null;
|
||||||
if (attrs && isInternalLink(attrs.href))
|
if (attrs && isInternalLink(attrs.href))
|
||||||
return (
|
return (
|
||||||
<ToolButton
|
<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.`,
|
t`Your account is now 100% secure against unauthorized logins.`,
|
||||||
sms: () => t`phone number`,
|
sms: () => t`phone number`,
|
||||||
app: () => t`authentication app`,
|
app: () => t`authentication app`,
|
||||||
mfaFallbackMethodText: (fallback: string, primary: string) =>
|
mfaFallbackMethodText: (
|
||||||
`You will now receive your 2FA codes on your ${
|
fallback: "app" | "sms" | "email",
|
||||||
strings[fallback as keyof typeof strings]
|
primary: "app" | "sms" | "email"
|
||||||
} in case you lose access to your ${
|
) =>
|
||||||
strings[primary as keyof typeof strings]
|
`You will now receive your 2FA codes on your ${strings[
|
||||||
}.`,
|
fallback
|
||||||
transactionStatusToText: (
|
]().toLocaleLowerCase()} in case you lose access to your ${strings[
|
||||||
key: keyof typeof TRANSACTION_STATUS | ({} & string)
|
primary
|
||||||
) => {
|
]().toLocaleLowerCase()}.`,
|
||||||
return key in TRANSACTION_STATUS
|
transactionStatusToText: {
|
||||||
? TRANSACTION_STATUS[key as keyof typeof TRANSACTION_STATUS]()
|
completed: () => t`Completed`,
|
||||||
: key;
|
refunded: () => t`"Refunded`,
|
||||||
|
partially_refunded: () => t`Partially refunded`,
|
||||||
|
disputed: () => t`Disputed`
|
||||||
},
|
},
|
||||||
viewReceipt: () => t`View receipt`,
|
viewReceipt: () => t`View receipt`,
|
||||||
customDictWords: (count: number) =>
|
customDictWords: (count: number) =>
|
||||||
|
|||||||
Reference in New Issue
Block a user