nimiq-vitepress-theme
Version:
Nimiq UI theme for VitePress
253 lines (252 loc) • 8.43 kB
JavaScript
import { join } from "pathe";
import { useData } from "vitepress";
import { computed, onBeforeUnmount, ref } from "vue";
import { toast } from "vue-sonner";
import { useChangelog } from "./useChangelog.mjs";
import { useNimiqConfig } from "./useNimiqConfig.mjs";
export function useSourceCode() {
const { page, frontmatter } = useData();
const { repoURL } = useChangelog();
const nimiqConfig = useNimiqConfig();
if (typeof window === "undefined") {
const fallbackCopied = ref(false);
const fallbackSupported = ref(false);
const emptyString = computed(() => "");
const copyOptions = computed(() => ({
markdownLink: false,
viewMarkdown: false,
chatgpt: false,
claude: false
}));
return {
showSourceCode: computed(() => false),
// expose the copy action during SSR to keep hydration order identical to the client
showCopyMarkdown: computed(() => true),
editUrl: emptyString,
sourceCodeUrl: emptyString,
sourceCodeLabel: computed(() => "View Source"),
copyMarkdownContent: async () => {
},
copied: fallbackCopied,
isSupported: fallbackSupported,
copyMarkdownLink: async () => {
},
chatGPTUrl: emptyString,
claudeUrl: emptyString,
viewAsMarkdown: () => {
},
copyOptionsConfig: copyOptions,
hasDropdownOptions: computed(() => false)
};
}
const copied = ref(false);
const isSupported = ref(false);
let resetTimer;
const determineSupport = () => {
if (typeof window === "undefined") {
isSupported.value = false;
return;
}
const nav = window.navigator;
const hasClipboardAPI = !!nav?.clipboard?.writeText;
const hasLegacySupport = typeof document !== "undefined" && typeof document.queryCommandSupported === "function" && document.queryCommandSupported("copy");
isSupported.value = hasClipboardAPI || hasLegacySupport;
};
const resetCopiedFlag = () => {
if (resetTimer)
clearTimeout(resetTimer);
resetTimer = setTimeout(() => {
copied.value = false;
resetTimer = void 0;
}, 1500);
};
const copy = async (text) => {
if (typeof window === "undefined")
throw new Error("Clipboard not available during SSR");
const nav = window.navigator;
if (nav?.clipboard?.writeText) {
await nav.clipboard.writeText(text);
} else if (typeof document !== "undefined" && document.body) {
const textarea = document.createElement("textarea");
textarea.value = text;
textarea.setAttribute("readonly", "true");
textarea.style.position = "absolute";
textarea.style.left = "-9999px";
document.body.appendChild(textarea);
textarea.select();
const successful = document.execCommand?.("copy") ?? false;
document.body.removeChild(textarea);
if (!successful)
throw new Error("Clipboard copy failed");
} else {
throw new Error("Clipboard API not supported");
}
copied.value = true;
resetCopiedFlag();
};
if (typeof window !== "undefined")
determineSupport();
onBeforeUnmount(() => {
if (resetTimer)
clearTimeout(resetTimer);
});
const getAbsolutePageUrl = () => {
if (typeof window === "undefined")
return "";
return window.location.href;
};
const getMarkdownUrl = () => {
if (typeof window === "undefined")
return "";
const url = new URL(window.location.href);
const pathname = url.pathname;
if (pathname.endsWith("/")) {
url.pathname = `${pathname}index.md`;
} else {
url.pathname = `${pathname}.md`;
}
return url.href;
};
const showSourceCode = computed(() => {
if (!isSupported.value)
return false;
const copyOptions = frontmatter.value.copyOptions;
if (copyOptions === "hidden")
return false;
if (copyOptions === "source-only")
return true;
return false;
});
const showCopyMarkdown = computed(() => {
const copyOptions = frontmatter.value.copyOptions;
if (copyOptions === "hidden")
return false;
if (copyOptions === "source-only")
return false;
return true;
});
const copyOptionsConfig = computed(() => {
const fm = frontmatter.value;
return {
markdownLink: fm.copyMarkdownLink !== false,
viewMarkdown: fm.copyViewMarkdown !== false,
chatgpt: fm.copyChatGPT !== false,
claude: fm.copyClaude !== false
};
});
const hasDropdownOptions = computed(() => {
const config = copyOptionsConfig.value;
return config.markdownLink || config.viewMarkdown || config.chatgpt || config.claude;
});
const effectiveRepoURL = computed(() => {
return nimiqConfig.repoURL || repoURL.value;
});
const getRepoFilePath = computed(() => {
const pathPrefix = frontmatter.value.sourceCodePathPrefix;
if (pathPrefix !== void 0) {
return pathPrefix ? join(pathPrefix, page.value.filePath) : page.value.filePath;
}
if (nimiqConfig.contentPath) {
return join(nimiqConfig.contentPath, page.value.filePath);
}
if (effectiveRepoURL.value && (effectiveRepoURL.value.includes("/nimiq-ui") || effectiveRepoURL.value.includes("/ui") || page.value.filePath.includes("docs/") === false)) {
return join("docs", page.value.filePath);
}
return page.value.filePath;
});
const editUrl = computed(() => {
if (!effectiveRepoURL.value)
return "";
return `${effectiveRepoURL.value.replace(/\/$/, "")}/${getRepoFilePath.value}`;
});
const sourceCodeUrl = computed(() => {
if (!effectiveRepoURL.value)
return "";
return `${effectiveRepoURL.value.replace(/\/$/, "")}/blob/main/${getRepoFilePath.value}`;
});
const sourceCodeLabel = computed(() => {
return "View Source";
});
const copyMarkdownContent = async () => {
try {
if (typeof window === "undefined") {
toast.error("Copy functionality not available during server-side rendering");
return;
}
const markdownUrl = getMarkdownUrl();
if (!markdownUrl) {
throw new Error("Could not determine markdown URL");
}
const response = await fetch(markdownUrl, {
headers: {
Accept: "text/markdown, text/plain, */*"
},
cache: "no-cache"
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const markdown = await response.text();
await copy(markdown);
toast.success("Page content copied to clipboard");
} catch (error) {
console.error("Failed to copy markdown content:", error);
toast.error("Failed to copy page content");
}
};
const copyMarkdownLink = async () => {
try {
if (typeof window === "undefined") {
toast.error("Copy functionality not available during server-side rendering");
return;
}
const pageTitle = page.value.title || document.title || "Documentation Page";
const markdownLink = `[${pageTitle}](${getAbsolutePageUrl()})`;
await copy(markdownLink);
toast.success("Markdown link copied to clipboard");
} catch (error) {
console.error("Failed to copy markdown link:", error);
toast.error("Failed to copy markdown link");
}
};
const chatGPTUrl = computed(() => {
const absoluteUrl = getAbsolutePageUrl();
if (!absoluteUrl)
return "";
const message = `Read ${absoluteUrl} so I can ask questions about it.`;
const encodedMessage = encodeURIComponent(message);
return `https://chatgpt.com/?hints=search&q=${encodedMessage}`;
});
const claudeUrl = computed(() => {
const absoluteUrl = getAbsolutePageUrl();
if (!absoluteUrl)
return "";
const message = `Read ${absoluteUrl} so I can ask questions about it.`;
const encodedMessage = encodeURIComponent(message);
return `https://claude.ai/new?q=${encodedMessage}`;
});
const viewAsMarkdown = () => {
if (typeof window === "undefined")
return;
const markdownUrl = getMarkdownUrl();
if (markdownUrl) {
window.open(markdownUrl, "_blank", "noopener,noreferrer");
}
};
return {
showSourceCode,
showCopyMarkdown,
editUrl,
sourceCodeUrl,
sourceCodeLabel,
copyMarkdownContent,
copied,
isSupported,
copyMarkdownLink,
chatGPTUrl,
claudeUrl,
viewAsMarkdown,
copyOptionsConfig,
hasDropdownOptions
};
}