diff --git a/.github/workflows/cjk-typography-ci.yml b/.github/workflows/cjk-typography-ci.yml new file mode 100644 index 000000000..e4c9fd67b --- /dev/null +++ b/.github/workflows/cjk-typography-ci.yml @@ -0,0 +1,44 @@ +name: CJK Typography CI + +on: + pull_request: + paths: + - "frontend/src/lib/cjkTypography.ts" + - "frontend/src/lib/tiptapCjkTypography.ts" + - "frontend/src/lib/markdownCommands.ts" + - "frontend/src/lib/__tests__/cjkTypography.test.ts" + - "frontend/src/components/CjkTypographyMenuActions.tsx" + - "frontend/src/components/MarkdownEditorImpl.tsx" + - "frontend/src/components/TiptapEditor.tsx" + - "frontend/src/components/TiptapEditorRuntime.tsx" + - "frontend/src/components/EditorPane.tsx" + - "frontend/src/components/editors/types.ts" + - "frontend/src/i18n/coverageTranslations.ts" + - ".github/workflows/cjk-typography-ci.yml" + push: + branches: ["release/v1.5.2"] + paths: + - "frontend/src/lib/cjkTypography.ts" + - "frontend/src/lib/tiptapCjkTypography.ts" + - "frontend/src/lib/__tests__/cjkTypography.test.ts" + +jobs: + cjk-typography: + runs-on: ubuntu-latest + defaults: + run: + working-directory: frontend + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: "22" + cache: npm + cache-dependency-path: frontend/package-lock.json + - run: npm ci + - name: Guarded transform, selection, and undo regressions + run: npm run test:run -- src/lib/__tests__/cjkTypography.test.ts + - name: Lint CJK typography modules + run: npx eslint src/lib/cjkTypography.ts src/lib/tiptapCjkTypography.ts src/components/CjkTypographyMenuActions.tsx + - name: Frontend TypeScript check + run: npx tsc -b diff --git a/frontend/src/components/CjkTypographyMenuActions.tsx b/frontend/src/components/CjkTypographyMenuActions.tsx new file mode 100644 index 000000000..6591be458 --- /dev/null +++ b/frontend/src/components/CjkTypographyMenuActions.tsx @@ -0,0 +1,40 @@ +import { Languages, Quote, TextCursorInput } from "lucide-react"; +import { useTranslation } from "react-i18next"; +import type { CjkTypographyAction } from "@/lib/cjkTypography"; + +const actions: Array<{ action: CjkTypographyAction; icon: typeof Languages }> = [ + { action: "spacing", icon: TextCursorInput }, + { action: "punctuation", icon: Languages }, + { action: "cornerQuotes", icon: Quote }, +]; + +/** Explicit one-click tools; no automatic input transformation or settings toggle. */ +export default function CjkTypographyMenuActions({ + onAction, disabled = false, mobile = false, +}: { + onAction: (action: CjkTypographyAction) => void; + disabled?: boolean; + mobile?: boolean; +}) { + const { t } = useTranslation(); + return ( +
+
+ {t("cjkTypography.title")} +
+ {actions.map(({ action, icon: Icon }) => ( + + ))} +
+ ); +} diff --git a/frontend/src/components/EditorPane.tsx b/frontend/src/components/EditorPane.tsx index 7fbdcd735..c2a5c13a8 100644 --- a/frontend/src/components/EditorPane.tsx +++ b/frontend/src/components/EditorPane.tsx @@ -109,6 +109,8 @@ import { type NoteFormatConversionRequest, } from "@/lib/noteFormatConversion"; import NoteThemeMenuSelect from "@/components/NoteThemeMenuSelect"; +import CjkTypographyMenuActions from "@/components/CjkTypographyMenuActions"; +import type { CjkTypographyAction } from "@/lib/cjkTypography"; import NoteColorMarkPicker from "@/components/NoteColorMarkPicker"; import type { NoteColorMark } from "@/types"; import MindMapEmbedInsertDialog from "@/components/MindMapEmbedInsertDialog"; @@ -1593,6 +1595,16 @@ function OrdinaryEditorPane({ }; }, [showDesktopMoreMenu]); + const applyCjkTypography = (action: CjkTypographyAction) => { + setShowMobileMenu(false); + setShowDesktopMoreMenu(false); + // The underlying editor checks read-only again, guarding stale menu state. + if (!activeNote || !canEditActiveNote || effectiveLocked || isTrashed || + modeSwitching || noteIsFullHtmlDoc) return; + const applied = editorHandleRef.current?.applyCjkTypography?.(action) === true; + if (!applied) toast.info(t("cjkTypography.noChanges")); + }; + const handleUpdate = useCallback(async (data: NoteEditorUpdatePayload) => { const currentNote = activeNoteRef.current; if (!currentNote || currentNote.isLocked || viewLockedIdsRef.current.has(currentNote.id)) return; @@ -2857,6 +2869,11 @@ const moveToTrash = useCallback(async () => { {t('editor.showOutline')} +
{/* AI ���ɱ��� */} + {canSplitDocument && onSplitDocument && (