diff --git a/app/app.config.ts b/app/app.config.ts new file mode 100644 index 0000000..6307c81 --- /dev/null +++ b/app/app.config.ts @@ -0,0 +1,17 @@ +// @keep-sorted +export default defineAppConfig({ + component: { + codeblock: { + /** 代码块触发折叠的行数 */ + triggerRows: 32, + /** 代码块折叠后的行数 */ + collapsedRows: 16, + /** 启用代码块缩进导航会关闭空格渲染 */ + enableIndentGuide: true, + /** 代码块缩进导航(Indent Guige)竖线匹配空格数 */ + indent: 4, + /** tab渲染宽度 */ + tabSize: 3, + }, + }, +}); diff --git a/app/assets/css/code.css b/app/assets/css/code.css new file mode 100644 index 0000000..bc316d4 --- /dev/null +++ b/app/assets/css/code.css @@ -0,0 +1,19 @@ +@layer utilities { + pre code .line { + @apply block relative min-h-[1rem]; + } +} + +pre code .line::before { + content: attr(data-line); + position: absolute; + top: 0; + left: -6.25rem; + color: #cccccc; + user-select: none; +} + +.dark pre code .line::before { + color: #777777; + opacity: 0.43; +} diff --git a/app/assets/css/main.css b/app/assets/css/main.css index 6bf24bd..13f0fca 100644 --- a/app/assets/css/main.css +++ b/app/assets/css/main.css @@ -5,6 +5,8 @@ @import "@nuxt/ui"; @plugin "@tailwindcss/typography"; @plugin "@tailwindcss/forms"; +@tailwind utilities; +@source "../../../content/**/*"; /* @import "@chinese-fonts/maple-mono-cn/dist/MapleMono-CN-Regular/results.css"; */ @custom-variant dark (&:where(.dark, .dark *)); diff --git a/app/components/content/ProsePre1.vue b/app/components/content/ProsePre1.vue new file mode 100644 index 0000000..e3e841e --- /dev/null +++ b/app/components/content/ProsePre1.vue @@ -0,0 +1,275 @@ + + + + + + {{ filename }} + + + {{ language }} + + + {{ isWrap ? "横向滚动" : "自动换行" }} + + + {{ copied ? "已复制" : "复制" }} + + + + + + + + + + {{ rows }} 行 + + + + + diff --git a/app/components/content/ProsePre.vue b/app/components/content/ProsePre2.vue similarity index 87% rename from app/components/content/ProsePre.vue rename to app/components/content/ProsePre2.vue index abfd5cc..0a68fcf 100644 --- a/app/components/content/ProsePre.vue +++ b/app/components/content/ProsePre2.vue @@ -36,6 +36,8 @@ const props = defineProps({ }, }); +console.log(props.code); + const meta = computed(() => { if (!props.meta) return {}; @@ -126,7 +128,37 @@ if (import.meta.client) { onMounted(() => { if (!codeblock.value) return; - const lines = codeblock.value.querySelectorAll(".line"); + // 兼容不同的代码块结构(包括 text 类型) + let lines = Array.from(codeblock.value.querySelectorAll(".line")); + + // 如果没有 .line 元素,按 \n 分割创建行 + if (lines.length === 0) { + const html = codeblock.value.innerHTML; + const textLines = html.split("\n"); + + // 清空预先存在的内容 + codeblock.value.innerHTML = ""; + + // 为每一行创建包装元素 + textLines.forEach((lineHtml, index) => { + const lineEl = document.createElement("div"); + lineEl.className = "line"; + lineEl.innerHTML = lineHtml; + lineEl.setAttribute("data-line", String(index + 1)); + codeblock.value!.appendChild(lineEl); + }); + + lines = Array.from(codeblock.value.querySelectorAll(".line")); + } else { + // 原有逻辑:为 .line 元素设置 data-line + lines.forEach((line, index) => { + if (!line.getAttribute("data-line")) { + line.setAttribute("data-line", String(index + 1)); + } + }); + } + + // 隐藏首尾空行 let firstNonEmptyIndex = -1; let lastNonEmptyIndex = -1; @@ -141,12 +173,6 @@ if (import.meta.client) { // 隐藏首尾空行 lines.forEach((line, index) => { - // 设置行号 - if (!line.getAttribute("data-line")) { - line.setAttribute("data-line", String(index + 1)); - } - - // 隐藏首尾空行 if (index < firstNonEmptyIndex || index > lastNonEmptyIndex) { (line as HTMLElement).style.display = "none"; } @@ -294,7 +320,7 @@ if (import.meta.client) { } :deep(code) { - font-family: "Fira Code", "Monaco", monospace; + /* font-family: "JetBrains Mono", "Fira Code", "Monaco", monospace; */ font-size: 0.95em; letter-spacing: 0.01em; font-variant-ligatures: common-ligatures; diff --git a/app/components/content/ProsePre3.vue b/app/components/content/ProsePre3.vue new file mode 100644 index 0000000..7c101e1 --- /dev/null +++ b/app/components/content/ProsePre3.vue @@ -0,0 +1,75 @@ + + + + + + {{ filename }} + + + + + + + + + + + + + + + + diff --git a/app/composables/useCopy.ts b/app/composables/useCopy.ts new file mode 100644 index 0000000..92be57e --- /dev/null +++ b/app/composables/useCopy.ts @@ -0,0 +1,18 @@ +/** + * 点击触发元素时,将文本复制到剪贴板,并在触发元素上显示提示信息。 + * @param target - 提供复制文本、目标元素或组件实例。可为字符串、输入框(复制其 `value`)或其他 HTMLElement(复制其文本内容)。 + */ +export default function useCopy( + target: MaybeRefOrGetter<{ $el: Element } | HTMLInputElement | Element | null> | string, +) { + const getEl = (element: any) => element?.$el ?? element; + const getText = () => { + const el = getEl(toValue(target)); + + if (typeof target === "string") return target; + if (el instanceof HTMLInputElement) return el.value; + return (el?.textContent as string) || ""; + }; + + return useClipboard({ source: getText, legacy: true }); +} diff --git a/nuxt.config.ts b/nuxt.config.ts index ca5bc7f..27d2d8e 100644 --- a/nuxt.config.ts +++ b/nuxt.config.ts @@ -6,17 +6,19 @@ export default defineNuxtConfig({ srcDir: "app", css: ["./app/assets/css/main.css"], - components: [ - { - path: "~/components/content", - extensions: ["vue"], - prefix: "", - }, - { - path: "~/components", - extensions: ["vue"], - }, - ], + components: { + dirs: [ + { + path: "~/components", + extensions: ["vue"], + }, + { + path: "~/components/content", + extensions: ["vue"], + prefix: "Prose", + }, + ], + }, future: { compatibilityVersion: 4,