From 6ad835d89fe1c90432d35a9e895968cfeb78b753 Mon Sep 17 00:00:00 2001 From: ZhymabekRoman Date: Tue, 10 Dec 2024 20:54:06 +0500 Subject: [PATCH] feature(shiki): make codeblock text cursor selectable --- new-web/src/app.css | 4 ++ new-web/svelte.config.js | 96 ++++++++++++++++++++++++++-------------- 2 files changed, 67 insertions(+), 33 deletions(-) diff --git a/new-web/src/app.css b/new-web/src/app.css index 41b165a..7561f58 100644 --- a/new-web/src/app.css +++ b/new-web/src/app.css @@ -143,4 +143,8 @@ html { html.dark pre.shiki .diff.remove { background-color: hsla(0 62.8% 30.6% / 0.7); } + + pre.shiki code { + @apply select-text cursor-text focus:outline-none; + } } diff --git a/new-web/svelte.config.js b/new-web/svelte.config.js index df87e43..5c5a741 100644 --- a/new-web/svelte.config.js +++ b/new-web/svelte.config.js @@ -1,11 +1,11 @@ -import { mdsvex, escapeSvelte } from 'mdsvex'; -import rehypeExternalLinks from 'rehype-external-links'; -import { createHighlighter } from 'shiki'; -import adapter from '@sveltejs/adapter-auto'; -import { toHtml } from 'hast-util-to-html'; -import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; -import { h } from 'hastscript'; -import remarkMixtapeEmbed from './src/lib/utils/remark/mixtape-embed.js'; +import { mdsvex, escapeSvelte } from "mdsvex"; +import rehypeExternalLinks from "rehype-external-links"; +import { createHighlighter } from "shiki"; +import adapter from "@sveltejs/adapter-auto"; +import { toHtml } from "hast-util-to-html"; +import { vitePreprocess } from "@sveltejs/vite-plugin-svelte"; +import { h } from "hastscript"; +import remarkMixtapeEmbed from "./src/lib/utils/remark/mixtape-embed.js"; const readySvg = "data:image/svg+xml;utf8,"; @@ -16,9 +16,9 @@ const successSvg = export function renderCodeCopyButton(code, options = {}) { const toggleMs = options.toggle || 3000; const button = h( - 'button', + "button", { - 'data-code': code, + "data-code": code, style: ` position: absolute; right: 1rem; @@ -54,11 +54,11 @@ export function renderCodeCopyButton(code, options = {}) { success.style.display = 'none'; }, ${toggleMs}); window.dispatchEvent(new CustomEvent('toast', { detail: { message: 'Copied to clipboard' } })); - ` + `, }, [ - h('span', { - class: 'ready', + h("span", { + class: "ready", style: ` width: 100%; aspect-ratio: 1 / 1; @@ -68,10 +68,10 @@ export function renderCodeCopyButton(code, options = {}) { background-image: url("${readySvg}"); z-index: 99; display: block; - ` + `, }), - h('span', { - class: 'success', + h("span", { + class: "success", style: ` width: 100%; aspect-ratio: 1 / 1; @@ -81,9 +81,9 @@ export function renderCodeCopyButton(code, options = {}) { background-image: url("${successSvg}"); z-index: 99; display: none; - ` - }) - ] + `, + }), + ], ); return toHtml(button); @@ -91,35 +91,65 @@ export function renderCodeCopyButton(code, options = {}) { /** @type {import('mdsvex').MdsvexOptions} */ const mdsvexOptions = { - extensions: ['.md'], + extensions: [".md"], remarkPlugins: [remarkMixtapeEmbed], - rehypePlugins: [[rehypeExternalLinks, { target: '_blank', rel: ['nofollow'] }]], + rehypePlugins: [ + [rehypeExternalLinks, { target: "_blank", rel: ["nofollow"] }], + ], highlight: { - highlighter: async (code, lang = 'text') => { + highlighter: async (code, lang = "text") => { const highlighter = await createHighlighter({ - themes: ['poimandres'], - langs: ['javascript', 'typescript'] + themes: ["poimandres"], + langs: ["javascript", "typescript"], }); - await highlighter.loadLanguage('javascript', 'typescript'); - const result = highlighter.codeToHtml(code, { + await highlighter.loadLanguage("javascript", "typescript"); + const result = highlighter.codeToHast(code, { lang, - theme: 'poimandres' + theme: "poimandres", + transformers: [ + { + code(node) { + node.properties = { + ...node.properties, + contenteditable: "true", + "aria-label": "code", + "aria-readonly": "true", + inputmode: "none", + tabindex: "0", + "aria-multiline": "true", + "aria-haspopup": "false", + "data-gramm": "false", + "data-gramm_editor": "false", + "data-enable-grammarly": "false", + spellcheck: "false", + autocorrect: "off", + autocapitalize: "none", + autocomplete: "off", + "data-ms-editor": "false", + }; + return node; + }, + }, + ], }); + const resultHtml = toHtml(result); const buttonHtml = renderCodeCopyButton(code, { toggle: 1200 }); - const html = escapeSvelte(`
${buttonHtml}${result}
`); + const html = escapeSvelte( + `
${buttonHtml}${resultHtml}
`, + ); return `{@html \`${html}\` }`; - } - } + }, + }, }; /** @type {import('@sveltejs/kit').Config} */ const config = { - extensions: ['.svelte', '.md'], + extensions: [".svelte", ".md"], preprocess: [vitePreprocess(), mdsvex(mdsvexOptions)], kit: { - adapter: adapter() - } + adapter: adapter(), + }, }; export default config;