editor: add ctrl+k to create link shortcut (#3501)

* editor: added link pop-up keyboard short-cut

* editor: Ctrl+K to create link

---------

Co-authored-by: Abdullah Atta <abdullahatta@streetwriters.co>
This commit is contained in:
Muhammad Ali
2024-09-13 11:10:50 +05:00
committed by GitHub
parent 020244231f
commit 18310698f6
8 changed files with 71 additions and 28 deletions

View File

@@ -21,6 +21,7 @@ import { Extension } from "@tiptap/core";
import { isInTable } from "@tiptap/pm/tables";
import { isListActive } from "../../utils/prosemirror";
import { CodeBlock } from "../code-block";
import { showLinkPopup } from "../../toolbar/popups/link-popup";
export const KeyMap = Extension.create({
name: "key-map",
@@ -65,6 +66,10 @@ export const KeyMap = Extension.create({
});
});
return true;
},
"Mod-k": ({ editor }) => {
showLinkPopup(editor);
return true;
}
};
}

View File

@@ -29,7 +29,6 @@ import {
} from "@tiptap/core";
import { Plugin, TextSelection } from "@tiptap/pm/state";
import { find, registerCustomProtocol, reset } from "linkifyjs";
import { autolink } from "./helpers/autolink";
import { clickHandler } from "./helpers/clickHandler";
import { pasteHandler } from "./helpers/pasteHandler";

View File

@@ -17,7 +17,7 @@ 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 { NodeWithOffset } from "@/src/utils/prosemirror";
import { NodeWithOffset } from "../../../utils/prosemirror";
import { createContext, useContext } from "react";
const HoverPopupContext = createContext<{

View File

@@ -21,7 +21,12 @@ import { Input } from "@theme-ui/components";
import { Flex } from "@theme-ui/components";
import { useRefValue } from "../../hooks/use-ref-value";
import { Popup } from "../components/popup";
import { LinkDefinition } from "../tools/link";
import { isInternalLink, LinkDefinition } from "../tools/link";
import { showPopup } from "../../components/popup-presenter";
import Link, { LinkAttributes } from "../../extensions/link";
import { ImageNode } from "../../extensions/image";
import { findMark, selectionToOffset } from "../../utils/prosemirror";
import { Editor, getMarkAttributes } from "@tiptap/core";
export type LinkPopupProps = {
link?: LinkDefinition;
@@ -84,3 +89,60 @@ export function LinkPopup(props: LinkPopupProps) {
</Popup>
);
}
export async function showLinkPopup(editor: Editor) {
const isActive = editor.isActive(Link.name);
const isImageActive = editor.isActive(ImageNode.name);
const selectedNode = selectionToOffset(editor.state);
const link = selectedNode?.node
? findMark(selectedNode.node, Link.name)
: null;
const attrs = link?.attrs || getMarkAttributes(editor.state, Link.name);
const selectedText = editor.state.doc.textBetween(
editor.state.selection.from,
editor.state.selection.to
);
if (isInternalLink(attrs.href)) {
const link = await editor.storage.createInternalLink?.(
attrs as LinkAttributes
);
if (!link) return;
if (isActive) {
const { from, to } = editor.state.selection;
if (selectedNode) editor.commands.setTextSelection(selectedNode);
editor.commands.setLink(link);
if (selectedNode) editor.commands.setTextSelection({ from, to });
} else {
editor.commands.setLink({ ...link, title: selectedText || link.title });
}
return;
}
showPopup({
popup: (close) => (
<LinkPopup
link={
selectedNode && selectedNode.node
? {
title: isActive ? selectedNode.node.textContent : selectedText,
href: link?.attrs.href || ""
}
: undefined
}
onClose={close}
onDone={(link) => {
if (isActive) {
if (selectedNode)
editor.chain().focus().setTextSelection(selectedNode).run();
editor.commands.setLink(link);
} else editor.commands.toggleLink(link);
close();
}}
isEditing={isActive}
isImageActive={isImageActive}
/>
),
mobile: "sheet",
desktop: "popup"
});
}

View File

@@ -25,7 +25,7 @@ import { MoreTools } from "../components/more-tools";
import { useToolbarLocation } from "../stores/toolbar-store";
import { ImageProperties as ImagePropertiesPopup } from "../popups/image-properties";
import { findSelectedNode } from "../../utils/prosemirror";
import { ImageAttributes } from "@/src/extensions/image";
import { ImageAttributes } from "../../extensions/image";
export function ImageSettings(props: ToolProps) {
const { editor } = props;

View File

@@ -315,6 +315,6 @@ function LinkTool(props: LinkToolProps) {
);
}
function isInternalLink(href?: string | null) {
export function isInternalLink(href?: string | null) {
return typeof href === "string" ? href.startsWith("nn://") : false;
}

View File

@@ -4,10 +4,6 @@
"lib": ["DOM", "DOM.Iterable", "ESNext"],
"jsx": "react-jsx",
"outDir": "./dist/",
"baseUrl": "./",
"paths": {
"@/*": ["*"]
},
"incremental": true
},
"exclude": ["src/**/*.test.ts"],

View File

@@ -1,19 +0,0 @@
{
"extends": "../../tsconfig",
"compilerOptions": {
"module": "commonjs",
"lib": ["DOM", "DOM.Iterable", "ESNext"],
"jsx": "react-jsx",
"outDir": "./dist/",
"baseUrl": "./",
"paths": {
"@/*": ["*"]
}
},
"ts-node": {
"transpileOnly": true,
"swc": true,
"require": ["tsconfig-paths/register"]
},
"include": ["src/"]
}