diff --git a/apps/renderer/src/modules/editor/css-editor.tsx b/apps/renderer/src/modules/editor/css-editor.tsx index a12f9e484..f84432f8b 100644 --- a/apps/renderer/src/modules/editor/css-editor.tsx +++ b/apps/renderer/src/modules/editor/css-editor.tsx @@ -6,6 +6,7 @@ import { useLayoutEffect, useRef } from "react" import css from "shiki/langs/css.mjs" import githubDark from "shiki/themes/github-dark.mjs" import githubLight from "shiki/themes/github-light.mjs" +import { useEventCallback } from "usehooks-ts" import { shiki } from "~/components/ui/code-highlighter/shiki/shared" @@ -19,6 +20,7 @@ export const CSSEditor: Component<{ const ref = useRef(null) const isDark = useIsDark() + const updateFnRef = useRef<() => void>() useLayoutEffect(() => { let dispose: () => void if (ref.current) { @@ -32,7 +34,7 @@ export const CSSEditor: Component<{ selection?.addRange(range) ref.current.textContent = defaultValue ?? "" - const { dispose: disposeShiki } = createPlainShiki(shiki).mount(ref.current, { + const { dispose: disposeShiki, update } = createPlainShiki(shiki).mount(ref.current, { lang: "css", themes: { light: "github-light", @@ -40,6 +42,7 @@ export const CSSEditor: Component<{ }, defaultTheme: isDark ? "dark" : "light", }) + updateFnRef.current = update dispose = disposeShiki } return () => dispose?.() @@ -67,6 +70,50 @@ export const CSSEditor: Component<{ }) }, }) + const handleCopy = useEventCallback((e: React.ClipboardEvent) => { + e.preventDefault() + e.clipboardData.setData("text/plain", ref.current?.textContent ?? "") + }) + const handlePaste = useEventCallback((e: React.ClipboardEvent) => { + e.preventDefault() + const text = e.clipboardData.getData("text/plain") + + const selection = window.getSelection() + if (selection && selection.rangeCount > 0) { + const range = selection.getRangeAt(0) + // Delete the current selection + range.deleteContents() + // Insert the text at the current cursor position + range.insertNode(document.createTextNode(text)) + + range.setStartAfter(range.endContainer) + range.setEndAfter(range.endContainer) + selection.removeAllRanges() + selection.addRange(range) + } + updateFnRef.current?.() + onChange(ref.current?.textContent ?? "") + // Focus the editor and set cursor to the end + if (ref.current) { + ref.current.focus() + const selection = window.getSelection() + const range = document.createRange() + range.selectNodeContents(ref.current) + range.collapse(false) // collapse to end + selection?.removeAllRanges() + selection?.addRange(range) + + // Get selection position + const rect = range.getBoundingClientRect() + const containerRect = ref.current.getBoundingClientRect() + const relativeBottom = rect.bottom - containerRect.top + + // If out of view, scroll to the position + if (relativeBottom > ref.current.clientHeight) { + ref.current.scrollTop += relativeBottom - ref.current.clientHeight + } + } + }) return (
{ variant="outline" onClick={(e) => { e.preventDefault() + setUISetting("customCSS", initialCSS.current) + forceUpdate() }} > diff --git a/locales/settings/zh-CN.json b/locales/settings/zh-CN.json index 4a9e5a239..862dd5d0c 100644 --- a/locales/settings/zh-CN.json +++ b/locales/settings/zh-CN.json @@ -84,7 +84,7 @@ "appearance.theme.light": "亮色", "appearance.theme.system": "跟随系统", "appearance.thumbnail_ratio.description": "文章列表缩略图比例", - "appearance.thumbnail_ratio.original": "原始图像", + "appearance.thumbnail_ratio.original": "原始比例", "appearance.thumbnail_ratio.square": "正方形", "appearance.thumbnail_ratio.title": "缩略比例", "appearance.title": "外观", @@ -155,7 +155,7 @@ "integration.instapaper.password.label": "密码", "integration.instapaper.title": "Instapaper", "integration.instapaper.username.label": "Instapaper 用户名", - "integration.obsidian.enable.description": "显示保存到Obsidian按钮(如果可用)", + "integration.obsidian.enable.description": "显示保存到 Obsidian 按钮(如果可用)", "integration.obsidian.enable.label": "启用", "integration.obsidian.title": "Obsidian", "integration.obsidian.vaultPath.description": "你的 Obsidian 仓库的路径", @@ -284,8 +284,8 @@ "wallet.ranking.rank": "排名", "wallet.ranking.title": "Power 排名", "wallet.rewardDescription.description1": "每日奖励基于「用户等级」和「活跃度」两部分计算", - "wallet.rewardDescription.description2": "用户等级: 由 Power 排行榜的排名决定", - "wallet.rewardDescription.description3": "用户活跃度: 使用 Follow 功能可以提升活跃度,活跃度的奖励倍数范围是 1x ~ 10x", + "wallet.rewardDescription.description2": "用户等级:由 Power 排行榜的排名决定", + "wallet.rewardDescription.description3": "用户活跃度:使用 Follow 功能可以提升活跃度,活跃度的奖励倍数范围是 1x ~ 10x", "wallet.rewardDescription.level": "用户等级", "wallet.rewardDescription.percentage": "排名比例", "wallet.rewardDescription.reward": "奖励倍数",