support anchor copy

This commit is contained in:
Philipinho
2026-02-09 14:33:36 -08:00
parent f7a9004c73
commit 4573dc1249
3 changed files with 27 additions and 15 deletions
+3 -14
View File
@@ -1,6 +1,7 @@
// Source: https://github.com/mantinedev/mantine/blob/master/packages/@mantine/hooks/src/use-clipboard/use-clipboard.ts
// polyfilled to support execCommand fallback
import { useState } from "react";
import { execCommandCopy } from "@docmost/editor-ext";
export type UseClipboardOptions = {
timeout?: number;
@@ -33,7 +34,7 @@ export function useClipboard(
.then(() => handleCopyResult(true))
.catch(() => {
try {
fallbackCopy(value);
execCommandCopy(value);
handleCopyResult(true);
} catch (err) {
setError(err instanceof Error ? err : new Error("Failed to copy"));
@@ -41,7 +42,7 @@ export function useClipboard(
});
} else {
try {
fallbackCopy(value);
execCommandCopy(value);
handleCopyResult(true);
} catch (err) {
setError(err instanceof Error ? err : new Error("Failed to copy"));
@@ -57,15 +58,3 @@ export function useClipboard(
return { copy, reset, error, copied };
}
function fallbackCopy(value: string): void {
const textarea = document.createElement("textarea");
textarea.value = value;
textarea.style.position = "fixed";
textarea.style.left = "-9999px";
textarea.style.top = "-9999px";
document.body.appendChild(textarea);
textarea.select();
document.execCommand("copy");
document.body.removeChild(textarea);
}
@@ -4,6 +4,7 @@ import TiptapHeading, {
import { mergeAttributes } from "@tiptap/react";
import { Decoration, DecorationSet } from "prosemirror-view";
import { Plugin } from "prosemirror-state";
import { copyToClipboard } from "../utils";
const copyIcon = `<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24"><!-- Icon from Material Symbols Light by Google - https://github.com/google/material-design-icons/blob/master/LICENSE --><path fill="currentColor" d="M10.616 16.077H7.077q-1.692 0-2.884-1.192T3 12t1.193-2.885t2.884-1.193h3.539v1H7.077q-1.27 0-2.173.904Q4 10.731 4 12t.904 2.173t2.173.904h3.539zM8.5 12.5v-1h7v1zm4.885 3.577v-1h3.538q1.27 0 2.173-.904Q20 13.269 20 12t-.904-2.173t-2.173-.904h-3.538v-1h3.538q1.692 0 2.885 1.192T21 12t-1.193 2.885t-2.884 1.193z"/></svg>`;
const successIcon = `<svg xmlns="http://www.w3.org/2000/svg" style="color: forestgreen;" width="18" height="18" viewBox="0 0 24 24"><!-- Icon from Material Symbols by Google - https://github.com/google/material-design-icons/blob/master/LICENSE --><path fill="currentColor" d="m10.6 16.6l7.05-7.05l-1.4-1.4l-5.65 5.65l-2.85-2.85l-1.4 1.4zM12 22q-2.075 0-3.9-.788t-3.175-2.137T2.788 15.9T2 12t.788-3.9t2.137-3.175T8.1 2.788T12 2t3.9.788t3.175 2.137T21.213 8.1T22 12t-.788 3.9t-2.137 3.175t-3.175 2.138T12 22"/></svg>`;
@@ -41,7 +42,7 @@ export const Heading = TiptapHeading.extend<TiptapHeadingOptions>({
const id = node.attrs.id;
const baseUrl = window.location.href.split('#')[0];
const url = `${baseUrl}#${id}`;
navigator.clipboard.writeText(url);
copyToClipboard(url);
linkBtnContent.innerHTML = successIcon;
setTimeout(
() => (linkBtnContent.innerHTML = copyIcon),
+22
View File
@@ -384,3 +384,25 @@ export function sanitizeUrl(url: string | undefined): string {
const alphabet = "abcdefghijklmnopqrstuvwxyz";
export const generateNodeId = customAlphabet(alphabet, 12);
export function copyToClipboard(text: string): void {
if ("clipboard" in navigator) {
navigator.clipboard.writeText(text).catch(() => {
execCommandCopy(text);
});
} else {
execCommandCopy(text);
}
}
export function execCommandCopy(text: string): void {
const textarea = document.createElement("textarea");
textarea.value = text;
textarea.style.position = "fixed";
textarea.style.left = "-9999px";
textarea.style.top = "-9999px";
document.body.appendChild(textarea);
textarea.select();
document.execCommand("copy");
document.body.removeChild(textarea);
}