diff --git a/apps/mobile/app/screens/editor/tiptap/use-editor-events.tsx b/apps/mobile/app/screens/editor/tiptap/use-editor-events.tsx
index 1ff17d92a..b44d9318d 100644
--- a/apps/mobile/app/screens/editor/tiptap/use-editor-events.tsx
+++ b/apps/mobile/app/screens/editor/tiptap/use-editor-events.tsx
@@ -20,7 +20,7 @@ along with this program. If not, see .
/* eslint-disable no-case-declarations */
/* eslint-disable @typescript-eslint/no-var-requires */
import { isFeatureAvailable, useAreFeaturesAvailable } from "@notesnook/common";
-import { ItemReference } from "@notesnook/core";
+import { ItemReference, parseInternalLink } from "@notesnook/core";
import type { Attachment } from "@notesnook/editor";
import { EditorEvents } from "@notesnook/editor-mobile/src/utils/editor-events";
import { NativeEvents } from "@notesnook/editor-mobile/src/utils/native-events";
@@ -471,9 +471,8 @@ export const useEditorEvents = (
relationType: "from",
title: strings.dataTypesPluralCamelCase.reminder(),
onAdd: async () => {
- const reminderFeature = await isFeatureAvailable(
- "activeReminders"
- );
+ const reminderFeature =
+ await isFeatureAvailable("activeReminders");
if (!reminderFeature.isAllowed) {
ToastManager.show({
type: "info",
@@ -533,7 +532,59 @@ export const useEditorEvents = (
downloadAttachment((editorMessage.value as Attachment)?.hash, true);
break;
}
+ case EditorEvents.getLinkData: {
+ const url = (editorMessage.value as any)?.url as string;
+ const link = parseInternalLink(url);
+ if (!link) return;
+ switch (link.type) {
+ case "note":
+ case "notebook":
+ case "tag": {
+ const table =
+ link.type === "note"
+ ? "notes"
+ : link.type === "notebook"
+ ? "notebooks"
+ : "tags";
+ const item = await db
+ .sql()
+ .selectFrom(table)
+ .where("id", "=", link.id)
+ .select("title")
+ .executeTakeFirst();
+ editor.postMessage(NativeEvents.resolve, {
+ resolverId: editorMessage.resolverId,
+ data: {
+ type: link.type,
+ title: item?.title || ""
+ }
+ });
+ break;
+ }
+ case "color": {
+ const color = await db
+ .sql()
+ .selectFrom("colors")
+ .where("id", "=", link.id)
+ .select(["title", "colorCode"])
+ .executeTakeFirst();
+
+ editor.postMessage(NativeEvents.resolve, {
+ resolverId: editorMessage.resolverId,
+ data: {
+ type: "color",
+ title: color?.title || "",
+ metadata: {
+ colorCode: color?.colorCode || ""
+ }
+ }
+ });
+ }
+ }
+
+ break;
+ }
case EditorEvents.getAttachmentData: {
const data = (editorMessage.value as any)?.attachment as Attachment;
diff --git a/apps/mobile/package-lock.json b/apps/mobile/package-lock.json
index f55e00efc..a4efbd1ba 100644
--- a/apps/mobile/package-lock.json
+++ b/apps/mobile/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "@notesnook/mobile",
- "version": "3.3.17",
+ "version": "3.3.18",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@notesnook/mobile",
- "version": "3.3.17",
+ "version": "3.3.18",
"hasInstallScript": true,
"license": "GPL-3.0-or-later",
"dependencies": {
diff --git a/apps/web/src/components/editor/tiptap.tsx b/apps/web/src/components/editor/tiptap.tsx
index a80977140..291f95441 100644
--- a/apps/web/src/components/editor/tiptap.tsx
+++ b/apps/web/src/components/editor/tiptap.tsx
@@ -69,6 +69,7 @@ import { UpgradeDialog } from "../../dialogs/buy-dialog/upgrade-dialog";
import { ConfirmDialog } from "../../dialogs/confirm";
import { strings } from "@notesnook/intl";
import { handleInternalLink } from "../../common";
+import { db } from "../../common/db";
export type OnChangeHandler = (
content: () => string,
@@ -426,6 +427,48 @@ function TipTap(props: TipTapProps) {
const link = parseInternalLink(url);
if (link) handleInternalLink(url, openInNewTab);
else window.open(url, "_blank");
+ },
+ getLinkData: async (url) => {
+ const link = parseInternalLink(url);
+ if (!link) return;
+
+ switch (link.type) {
+ case "note":
+ case "notebook":
+ case "tag": {
+ const table =
+ link.type === "note"
+ ? "notes"
+ : link.type === "notebook"
+ ? "notebooks"
+ : "tags";
+ const item = await db
+ .sql()
+ .selectFrom(table)
+ .where("id", "=", link.id)
+ .select("title")
+ .executeTakeFirst();
+ return {
+ type: link.type,
+ title: item?.title || ""
+ };
+ }
+ case "color": {
+ const color = await db
+ .sql()
+ .selectFrom("colors")
+ .where("id", "=", link.id)
+ .select(["title", "colorCode"])
+ .executeTakeFirst();
+ return {
+ type: "color",
+ title: color?.title || "",
+ metadata: {
+ colorCode: color?.colorCode || ""
+ }
+ };
+ }
+ }
}
};
}, [
diff --git a/packages/editor-mobile/package-lock.json b/packages/editor-mobile/package-lock.json
index 24441ca6c..7c735f048 100644
--- a/packages/editor-mobile/package-lock.json
+++ b/packages/editor-mobile/package-lock.json
@@ -68,7 +68,7 @@
"@notesnook/intl": "file:../intl",
"@notesnook/theme": "file:../theme",
"@notesnook/ui": "file:../ui",
- "@social-embed/lib": "^0.1.0-next.7",
+ "@social-embed/lib": "^0.1.0-next.11",
"@tiptap/core": "2.6.6",
"@tiptap/extension-blockquote": "^2.6.6",
"@tiptap/extension-bullet-list": "^2.6.6",
diff --git a/packages/editor-mobile/src/components/editor.tsx b/packages/editor-mobile/src/components/editor.tsx
index 697893636..66893f6cf 100644
--- a/packages/editor-mobile/src/components/editor.tsx
+++ b/packages/editor-mobile/src/components/editor.tsx
@@ -162,6 +162,11 @@ const Tiptap = ({
attachment
) as Promise;
},
+ getLinkData: (url: string) => {
+ return postAsyncWithTimeout(EditorEvents.getLinkData, {
+ url: url
+ });
+ },
createInternalLink(attributes) {
return postAsyncWithTimeout(EditorEvents.createInternalLink, {
attributes
diff --git a/packages/editor-mobile/src/utils/editor-events.ts b/packages/editor-mobile/src/utils/editor-events.ts
index 83b8da698..c69673eb9 100644
--- a/packages/editor-mobile/src/utils/editor-events.ts
+++ b/packages/editor-mobile/src/utils/editor-events.ts
@@ -55,5 +55,6 @@ export const EditorEvents = {
goForward: "editor-events:go-forward",
saveScroll: "editor-events:save-scroll",
newNote: "editor-events:new-note",
- downloadCsv: "editor-events:download-csv"
+ downloadCsv: "editor-events:download-csv",
+ getLinkData: "editor-events:get-link-data"
} as const;
diff --git a/packages/editor/src/index.ts b/packages/editor/src/index.ts
index 122c4bc77..cc8449401 100644
--- a/packages/editor/src/index.ts
+++ b/packages/editor/src/index.ts
@@ -88,12 +88,14 @@ import { strings } from "@notesnook/intl";
import { InlineCode } from "./extensions/inline-code/inline-code.js";
import { FontLigature } from "./extensions/font-ligature/font-ligature.js";
import { SearchResult } from "./extensions/search-result/search-result.js";
+import { LinkData } from "./types.js";
interface TiptapStorage {
dateFormat?: DateTimeOptions["dateFormat"];
timeFormat?: DateTimeOptions["timeFormat"];
dayFormat?: DateTimeOptions["dayFormat"];
openLink?: (url: string, openInNewTab?: boolean) => void;
+ getLinkData?: (url: string) => Promise;
downloadAttachment?: (attachment: Attachment) => void;
openAttachmentPicker?: (type: AttachmentType) => void;
previewAttachment?: (attachment: Attachment) => void;
@@ -148,6 +150,7 @@ const useTiptap = (
openAttachmentPicker,
previewAttachment,
openLink,
+ getLinkData,
onBeforeCreate,
dateFormat,
timeFormat,
@@ -285,7 +288,13 @@ const useTiptap = (
}).configure({
openOnClick: !isMobile,
autolink: false,
- linkOnPaste: true
+ linkOnPaste: true,
+ protocols: [
+ {
+ scheme: "nn",
+ optionalSlashes: true
+ }
+ ]
}),
Table.configure({
resizable: true,
@@ -395,6 +404,7 @@ const useTiptap = (
editor.storage.createInternalLink = createInternalLink;
editor.storage.getAttachmentData = getAttachmentData;
editor.storage.downloadCsvTable = downloadCsvTable;
+ editor.storage.getLinkData = getLinkData;
if (onBeforeCreate) onBeforeCreate({ editor });
},
diff --git a/packages/editor/src/toolbar/tools/link.tsx b/packages/editor/src/toolbar/tools/link.tsx
index 266ac363c..d42831d6e 100644
--- a/packages/editor/src/toolbar/tools/link.tsx
+++ b/packages/editor/src/toolbar/tools/link.tsx
@@ -19,20 +19,24 @@ along with this program. If not, see .
import { ToolProps } from "../types.js";
import { ToolButton } from "../components/tool-button.js";
-import { useRef, useState } from "react";
+import { useEffect, useRef, useState } from "react";
import { ResponsivePresenter } from "../../components/responsive/index.js";
import { LinkPopup } from "../popups/link-popup.js";
import { useToolbarLocation } from "../stores/toolbar-store.js";
import { MoreTools } from "../components/more-tools.js";
import { useRefValue } from "../../hooks/use-ref-value.js";
import { findMark, selectionToOffset } from "../../utils/prosemirror.js";
-import { Flex, Link } from "@theme-ui/components";
+import { Flex, Link, Text } from "@theme-ui/components";
import { ImageNode } from "../../extensions/image/index.js";
import { Link as LinkNode } from "../../extensions/link/index.js";
import { getMarkAttributes } from "@tiptap/core";
import { useHoverPopupContext } from "../floating-menus/hover-popup/context.js";
import { strings } from "@notesnook/intl";
import { find } from "linkifyjs";
+import { Icons } from "../icons.js";
+import { mdiNoteOutline, mdiBookOutline, mdiPound } from "@mdi/js";
+import { Icon } from "@notesnook/ui";
+import { LinkData } from "../../types.js";
export function LinkSettings(props: ToolProps) {
const { editor } = props;
@@ -204,36 +208,62 @@ export function OpenLink(props: ToolProps) {
);
const { node } = selectedNode.current || {};
const link = node ? findMark(node, "link") : null;
- if (!link) return null;
- const href = link?.attrs.href;
- if (!href) return null;
+ const href = link?.attrs.href ?? null;
+ const [loading, setLoading] = useState(false);
+ const [linkData, setLinkData] = useState(undefined);
+
+ useEffect(() => {
+ if (!href) return;
+
+ (async () => {
+ try {
+ setLoading(true);
+ const result = await editor.storage.getLinkData?.(href);
+ setLinkData(result);
+ setLoading(false);
+ } catch (e) {
+ setLoading(false);
+ }
+ })();
+ }, [href]);
+
+ if (!link || !href) return null;
+
+ const title = linkData?.title || href;
return (
- {
- e.preventDefault();
- editor.storage.openLink?.(href);
- hide();
- }}
- target="_blank"
- variant="body"
- sx={{
- fontSize: "subBody",
- fontFamily: "body",
- mr: 1,
- color: "accent",
- maxWidth: [150, 250],
- overflow: "hidden",
- textOverflow: "ellipsis",
- whiteSpace: "nowrap",
- ":visited": { color: "accent" },
- ":hover": { color: "accent", opacity: 0.8 }
- }}
- >
- {href}
-
+ {linkData?.type && (
+
+ )}
+ {loading ? (
+ {strings.loading()}
+ ) : (
+ {
+ e.preventDefault();
+ editor.storage.openLink?.(href);
+ hide();
+ }}
+ target="_blank"
+ variant="body"
+ sx={{
+ fontSize: "subBody",
+ fontFamily: "body",
+ mr: 4,
+ color: "accent",
+ maxWidth: [150, 250],
+ overflow: "hidden",
+ textOverflow: "ellipsis",
+ whiteSpace: "nowrap",
+ ":visited": { color: "accent" },
+ ":hover": { color: "accent", opacity: 0.8 }
+ }}
+ >
+ {title}
+
+ )}
= {
+ note: mdiNoteOutline,
+ notebook: mdiBookOutline,
+ tag: mdiPound,
+ color: Icons.circle
+};
+
+function LinkTypeIcon({
+ type,
+ metadata
+}: {
+ type: LinkData["type"];
+ metadata?: LinkData["metadata"];
+}) {
+ const path = LINK_TYPE_ICONS[type];
+ if (!path) return null;
+
+ const color =
+ type === "color" && metadata?.colorCode
+ ? metadata.colorCode
+ : "icon-secondary";
+
+ return ;
+}
+
export function isInternalLink(href?: string | null) {
return typeof href === "string" ? href.startsWith("nn://") : false;
}
diff --git a/packages/editor/src/types.ts b/packages/editor/src/types.ts
index a906b2144..9df0313fc 100644
--- a/packages/editor/src/types.ts
+++ b/packages/editor/src/types.ts
@@ -25,6 +25,12 @@ export type PermissionRequestEvent = CustomEvent<{
silent: boolean;
}>;
+export type LinkData = {
+ type: "note" | "notebook" | "color" | "tag";
+ title?: string;
+ metadata?: Record;
+};
+
export class Editor extends TiptapEditor {
private mutex: Mutex = new Mutex();