@lumir-company/editor 0.4.21 → 0.4.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -0
- package/dist/index.d.mts +21 -15
- package/dist/index.d.ts +21 -15
- package/dist/index.js +852 -661
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +530 -346
- package/dist/index.mjs.map +1 -1
- package/dist/style.css +59 -0
- package/package.json +1 -1
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/components/LumirEditor.tsx","../src/utils/cn.ts","../src/utils/s3-uploader.ts","../src/blocks/HtmlPreview.tsx","../src/blocks/LinkPreview.tsx","../src/blocks/defaultLogo.ts","../src/blocks/VideoBlock.tsx","../src/blocks/columns/ColumnList.ts","../src/blocks/columns/columnNormalization.ts","../src/blocks/columns/columnDnd.ts","../src/blocks/columns/mergeColumns.ts","../src/blocks/columns/Column.ts","../src/styles/FontSizeStyle.tsx","../src/components/FloatingMenu/index.tsx","../src/components/FloatingMenu/Icons.tsx","../src/components/FloatingMenu/components/ToolbarDivider.tsx","../src/components/FloatingMenu/components/UndoRedoButtons.tsx","../src/components/FloatingMenu/components/TextStyleButton.tsx","../src/components/FloatingMenu/components/AlignButton.tsx","../src/utils/prosemirror-table-utils.ts","../src/components/FloatingMenu/components/ListButton.tsx","../src/components/FloatingMenu/components/ImageButton.tsx","../src/components/FloatingMenu/components/ColorButton.tsx","../src/constants/colors.ts","../src/components/FloatingMenu/components/FontSizeButton.tsx","../src/components/FloatingMenu/components/LinkButton.tsx","../src/components/FloatingMenu/components/TableButton.tsx","../src/components/FloatingMenu/components/HTMLImportButton.tsx","../src/components/FloatingMenu/components/BlockTypeSelect.tsx","../src/errors/LumirEditorError.ts","../src/extensions/VerticalAlignmentExtension.ts","../src/extensions/RowHeightExtension.ts","../src/extensions/rowResizing.ts","../src/constants/limits.ts","../src/extensions/tableScaling.ts","../src/extensions/tableCellAttrPreserve.ts","../src/extensions/TableAlignmentExtension.ts","../src/extensions/TableSelectAllExtension.ts","../src/blocks/columns/insertColumns.ts","../src/components/CustomFormattingToolbar.tsx","../src/components/TextAlignButtonWithVA.tsx","../src/components/VerticalAlignButton.tsx","../src/components/TableAlignButton.tsx","../src/components/FontSizeButton.tsx","../src/components/color/LumirColorControls.tsx","../src/components/LumirDragHandleMenu.tsx","../src/components/LumirTableHandlesController.tsx","../src/components/hooks/useFocusedCellHandlePositioning.ts","../src/utils/table-vertical-alignment.ts","../src/utils/font-size-serialization.ts","../src/utils/table-delete.ts","../src/utils/excel-paste.ts","../src/utils/table-paste-fit.ts"],"sourcesContent":["\"use client\";\r\n\r\n// 컴포넌트 및 유틸리티 export\r\nexport {\r\n default as LumirEditor,\r\n ContentUtils,\r\n EditorConfig,\r\n} from \"./components/LumirEditor\";\r\nexport { cn } from \"./utils/cn\";\r\nexport { createS3Uploader } from \"./utils/s3-uploader\";\r\nexport { HtmlPreviewBlock, schema as HtmlPreviewSchema } from \"./blocks/HtmlPreview\";\r\nexport { LinkPreviewBlock, fetchLinkMetadata, clearMetadataCache } from \"./blocks/LinkPreview\";\r\nexport type { LinkMetadata } from \"./blocks/LinkPreview\";\r\n\r\n// FloatingMenu 및 관련 컴포넌트 export\r\nexport { FloatingMenu } from \"./components/FloatingMenu\";\r\n\r\n// 글자 크기(fontSize) 관련 export\r\n// - FontSize: 커스텀 스타일 스펙, FONT_SIZE_PRESETS: 툴바 프리셋\r\n// - liftFontSize/lowerFontSize: 직렬화 호환 레이어(형제 키 ↔ styles.fontSize).\r\n// BlockNote 외부 렌더러에서 저장 JSON을 재수화할 때 liftFontSize를 사용한다.\r\nexport { FontSize, FONT_SIZE_PRESETS } from \"./styles/FontSizeStyle\";\r\nexport type { FontSizePreset } from \"./styles/FontSizeStyle\";\r\nexport { FontSizeButton } from \"./components/FontSizeButton\";\r\nexport {\r\n liftFontSize,\r\n lowerFontSize,\r\n} from \"./utils/font-size-serialization\";\r\nexport type { SerializedStyledText } from \"./utils/font-size-serialization\";\r\n\r\n// 에러 클래스 export\r\nexport { LumirEditorError } from \"./errors/LumirEditorError\";\r\nexport type { LumirErrorCode, LumirErrorDetails } from \"./errors/LumirEditorError\";\r\n\r\n// 색상 상수 export\r\nexport {\r\n TEXT_COLORS,\r\n BACKGROUND_COLORS,\r\n getHexFromColorValue,\r\n} from \"./constants/colors\";\r\nexport type { ColorItem } from \"./constants/colors\";\r\n\r\n// 타입 export (별도 파일에서 관리)\r\nexport type {\r\n LumirEditorProps,\r\n EditorType,\r\n DefaultPartialBlock,\r\n DefaultBlockSchema,\r\n DefaultInlineContentSchema,\r\n DefaultStyleSchema,\r\n PartialBlock,\r\n BlockNoteEditor,\r\n} from \"./types\";\r\nexport type { S3UploaderConfig } from \"./utils/s3-uploader\";\r\n","\"use client\";\r\n\r\nimport { useEffect, useMemo, useCallback, useState, useRef } from \"react\";\r\nimport {\r\n useCreateBlockNote,\r\n SideMenu as BlockSideMenu,\r\n SideMenuController,\r\n DragHandleButton,\r\n SuggestionMenuController,\r\n getDefaultReactSlashMenuItems,\r\n LinkToolbarController,\r\n FormattingToolbarController,\r\n useBlockNoteEditor,\r\n useComponentsContext,\r\n EditLinkButton,\r\n OpenLinkButton,\r\n DeleteLinkButton,\r\n} from \"@blocknote/react\";\r\nimport { BlockNoteView } from \"@blocknote/mantine\";\r\nimport { insertOrUpdateBlock } from \"@blocknote/core\";\r\nimport { ko, en } from \"@blocknote/core/locales\";\r\nimport { cn } from \"../utils/cn\";\r\n\r\nimport type { DefaultPartialBlock, LumirEditorProps } from \"../types\";\r\n\r\nimport { createS3Uploader } from \"../utils/s3-uploader\";\r\nimport { schema } from \"../blocks/HtmlPreview\";\r\nimport { FloatingMenu } from \"./FloatingMenu\";\r\nimport { LumirEditorError } from \"../errors/LumirEditorError\";\r\nimport { VerticalAlignmentExtension } from \"../extensions/VerticalAlignmentExtension\";\r\nimport { RowHeightExtension } from \"../extensions/RowHeightExtension\";\r\nimport { TableAlignmentExtension } from \"../extensions/TableAlignmentExtension\";\r\nimport { TableSelectAllExtension } from \"../extensions/TableSelectAllExtension\";\r\nimport { insertTwoColumns } from \"../blocks/columns/insertColumns\";\r\nimport { CustomFormattingToolbar } from \"./CustomFormattingToolbar\";\r\nimport { LumirDragHandleMenu } from \"./LumirDragHandleMenu\";\r\nimport { LumirTableHandlesController } from \"./LumirTableHandlesController\";\r\nimport {\r\n injectTableCellAttrs,\r\n injectTableBlockAttrs,\r\n} from \"../utils/table-vertical-alignment\";\r\nimport {\r\n liftFontSize,\r\n lowerFontSize,\r\n} from \"../utils/font-size-serialization\";\r\nimport { isInTableCell } from \"../utils/prosemirror-table-utils\";\r\nimport { removeFocusedRowOrColumn } from \"../utils/table-delete\";\r\nimport { normalizeExcelTableHtml } from \"../utils/excel-paste\";\r\nimport {\r\n computeFittedColumnWidthsPerTable,\r\n collectTableBlocks,\r\n applyFittedWidthsToNewTables,\r\n} from \"../utils/table-paste-fit\";\r\nimport {\r\n MAX_FILE_SIZE,\r\n MAX_VIDEO_FILE_SIZE,\r\n BLOCKED_EXTENSIONS,\r\n ALLOWED_VIDEO_MIME_TYPES,\r\n ALLOWED_VIDEO_EXTENSIONS,\r\n} from \"../constants/limits\";\r\n\r\n// #region agent log\r\nconst DEBUG_LOG = (loc: string, msg: string, data: Record<string, unknown>) => {\r\n const p = fetch(\"http://127.0.0.1:7686/ingest/1f8ee1c5-0cf0-4ae7-91ed-5ea7ed17130a\", {\r\n method: \"POST\",\r\n headers: { \"Content-Type\": \"application/json\", \"X-Debug-Session-Id\": \"b73262\" },\r\n body: JSON.stringify({\r\n sessionId: \"b73262\",\r\n location: loc,\r\n message: msg,\r\n data,\r\n timestamp: Date.now(),\r\n }),\r\n });\r\n if (p && typeof (p as Promise<unknown>).catch === \"function\") (p as Promise<unknown>).catch(() => {});\r\n};\r\n// #endregion\r\n\r\n// ==========================================\r\n// 유틸리티 클래스들\r\n// ==========================================\r\n\r\n/**\r\n * 콘텐츠 관리 유틸리티\r\n * 기본 블록 생성 및 콘텐츠 검증 로직을 담당\r\n */\r\nexport class ContentUtils {\r\n /**\r\n * JSON 문자열의 유효성을 검증합니다\r\n * @param jsonString 검증할 JSON 문자열\r\n * @returns 유효한 JSON 문자열인지 여부\r\n */\r\n static isValidJSONString(jsonString: string): boolean {\r\n try {\r\n const parsed = JSON.parse(jsonString);\r\n return Array.isArray(parsed);\r\n } catch {\r\n return false;\r\n }\r\n }\r\n\r\n /**\r\n * JSON 문자열을 DefaultPartialBlock 배열로 파싱합니다\r\n * @param jsonString JSON 문자열\r\n * @returns 파싱된 블록 배열 또는 null (파싱 실패 시)\r\n */\r\n static parseJSONContent(jsonString: string): DefaultPartialBlock[] | null {\r\n try {\r\n const parsed = JSON.parse(jsonString);\r\n if (Array.isArray(parsed)) {\r\n return parsed as DefaultPartialBlock[];\r\n }\r\n return null;\r\n } catch {\r\n return null;\r\n }\r\n }\r\n\r\n /**\r\n * 기본 paragraph 블록 생성\r\n * @returns 기본 설정이 적용된 DefaultPartialBlock\r\n */\r\n static createDefaultBlock(): DefaultPartialBlock {\r\n return {\r\n type: \"paragraph\",\r\n props: {\r\n textColor: \"default\",\r\n backgroundColor: \"default\",\r\n textAlignment: \"left\",\r\n },\r\n content: [{ type: \"text\", text: \"\", styles: {} }],\r\n children: [],\r\n };\r\n }\r\n\r\n /**\r\n * 콘텐츠 유효성 검증 및 기본값 설정\r\n * @param content 사용자 제공 콘텐츠 (객체 배열 또는 JSON 문자열)\r\n * @param emptyBlockCount 빈 블록 개수 (기본값: 3)\r\n * @returns 검증된 콘텐츠 배열\r\n */\r\n static validateContent(\r\n content?: DefaultPartialBlock[] | string,\r\n emptyBlockCount: number = 3\r\n ): DefaultPartialBlock[] {\r\n // 1. 문자열인 경우 JSON 파싱 시도\r\n if (typeof content === \"string\") {\r\n if (content.trim() === \"\") {\r\n return this.createEmptyBlocks(emptyBlockCount);\r\n }\r\n\r\n const parsedContent = this.parseJSONContent(content);\r\n if (parsedContent && parsedContent.length > 0) {\r\n return parsedContent;\r\n }\r\n\r\n // 파싱 실패 시 빈 블록 생성\r\n return this.createEmptyBlocks(emptyBlockCount);\r\n }\r\n\r\n // 2. 배열인 경우 기존 로직\r\n if (!content || content.length === 0) {\r\n return this.createEmptyBlocks(emptyBlockCount);\r\n }\r\n\r\n return content;\r\n }\r\n\r\n /**\r\n * 빈 블록들을 생성합니다\r\n * @param emptyBlockCount 생성할 블록 개수\r\n * @returns 생성된 빈 블록 배열\r\n */\r\n private static createEmptyBlocks(\r\n emptyBlockCount: number\r\n ): DefaultPartialBlock[] {\r\n return Array.from({ length: emptyBlockCount }, () =>\r\n this.createDefaultBlock()\r\n );\r\n }\r\n}\r\n\r\n/**\r\n * 에디터 설정 관리 유틸리티\r\n * 각종 설정의 기본값과 검증 로직을 담당\r\n */\r\nexport class EditorConfig {\r\n /**\r\n * 테이블 설정 기본값 적용\r\n * @param userTables 사용자 테이블 설정\r\n * @returns 기본값이 적용된 테이블 설정\r\n */\r\n static getDefaultTableConfig(userTables?: LumirEditorProps[\"tables\"]) {\r\n return {\r\n splitCells: userTables?.splitCells ?? true,\r\n cellBackgroundColor: userTables?.cellBackgroundColor ?? true,\r\n cellTextColor: userTables?.cellTextColor ?? true,\r\n headers: userTables?.headers ?? true,\r\n };\r\n }\r\n\r\n /**\r\n * 헤딩 설정 기본값 적용\r\n * @param userHeading 사용자 헤딩 설정\r\n * @returns 기본값이 적용된 헤딩 설정\r\n */\r\n static getDefaultHeadingConfig(userHeading?: LumirEditorProps[\"heading\"]) {\r\n return userHeading?.levels && userHeading.levels.length > 0\r\n ? userHeading\r\n : { levels: [1, 2, 3, 4, 5, 6] as (1 | 2 | 3 | 4 | 5 | 6)[] };\r\n }\r\n\r\n /**\r\n * 비활성화할 확장 기능 목록 생성\r\n * @param userExtensions 사용자 정의 비활성 확장\r\n * @param allowVideo 비디오 업로드 허용 여부\r\n * @param allowAudio 오디오 업로드 허용 여부\r\n * @param allowFile 일반 파일 업로드 허용 여부\r\n * @returns 비활성화할 확장 기능 목록\r\n */\r\n static getDisabledExtensions(\r\n userExtensions?: string[],\r\n allowVideo = false,\r\n allowAudio = false,\r\n allowFile = false\r\n ): string[] {\r\n const set = new Set<string>(userExtensions ?? []);\r\n if (!allowVideo) set.add(\"video\");\r\n if (!allowAudio) set.add(\"audio\");\r\n if (!allowFile) set.add(\"file\");\r\n return Array.from(set);\r\n }\r\n}\r\n\r\n// 파일 타입 검증 함수\r\n/** @internal 테스트용 export */\r\nexport const isImageFile = (file: File, maxSize?: number): boolean => {\r\n const limit = maxSize !== undefined ? maxSize : MAX_FILE_SIZE;\r\n if (file.size === 0 || file.size > limit) {\r\n return false;\r\n }\r\n\r\n // 🔒 보안: SVG 파일 차단 (XSS 방지)\r\n const fileName = file.name?.toLowerCase() || \"\";\r\n if (\r\n file.type === \"image/svg+xml\" ||\r\n BLOCKED_EXTENSIONS.some((ext) => fileName.endsWith(ext))\r\n ) {\r\n return false;\r\n }\r\n\r\n // 이미지 타입 검증\r\n return (\r\n file.type?.startsWith(\"image/\") ||\r\n (!file.type && /\\.(png|jpe?g|gif|webp|bmp)$/i.test(fileName))\r\n );\r\n};\r\n\r\n/** @internal 테스트용 export */\r\nexport const isVideoFile = (file: File, maxSize?: number): boolean => {\r\n const limit = maxSize !== undefined ? maxSize : MAX_VIDEO_FILE_SIZE;\r\n const sizeOk = file.size > 0 && file.size <= limit;\r\n const fileName = file.name?.toLowerCase() || \"\";\r\n const mimeMatch = ALLOWED_VIDEO_MIME_TYPES.has(file.type);\r\n const videoPrefix = typeof file.type === \"string\" && file.type.startsWith(\"video/\");\r\n const extMatch = !file.type && ALLOWED_VIDEO_EXTENSIONS.some((ext) => fileName.endsWith(ext));\r\n const result = sizeOk && (mimeMatch || videoPrefix || extMatch);\r\n // #region agent log\r\n DEBUG_LOG(\"isVideoFile:check\", \"result\", {\r\n fileName: file.name,\r\n fileType: file.type,\r\n fileSize: file.size,\r\n sizeOk,\r\n mimeMatch,\r\n videoPrefix,\r\n extMatch,\r\n result,\r\n });\r\n // #endregion\r\n return result;\r\n};\r\n\r\n/**\r\n * floatingMenu(상단 고정 툴바) 사용 시 팝업 포매팅 툴바를 숨길 선택인지 판별.\r\n *\r\n * - 일반 텍스트 선택 → true (상단 툴바가 모든 텍스트 도구를 제공하므로 팝업 중복.\r\n * 또한 상단 툴바 상호작용 직후 팝업이 잘못된 위치(0,0)로 재표시되는 문제 방지)\r\n * - 테이블 셀 컨텍스트(CellSelection·셀 내부 커서) → false\r\n * (셀 병합·세로 정렬·셀 배경 등 상단 툴바에 없는 도구가 팝업에만 있음)\r\n * - NodeSelection(이미지/비디오 등) → false (캡션·교체·다운로드 등 파일 도구 유지)\r\n *\r\n * @internal 테스트용 export\r\n */\r\nexport const isSuppressiblePopupSelection = (\r\n editor: any,\r\n selection: any\r\n): boolean => {\r\n if (!selection) return false;\r\n if (selection.node) return false; // NodeSelection\r\n if (isInTableCell(editor)) return false; // CellSelection 또는 셀 내부 커서\r\n return true;\r\n};\r\n\r\n/** @internal 테스트용 export */\r\nexport const isHtmlFile = (file: File): boolean => {\r\n return (\r\n file.size > 0 &&\r\n (file.type === \"text/html\" ||\r\n file.name?.toLowerCase().endsWith(\".html\") ||\r\n file.name?.toLowerCase().endsWith(\".htm\"))\r\n );\r\n};\r\n\r\n// ============================================\r\n// 🔒 보안 유틸리티 함수\r\n// ============================================\r\n\r\n/**\r\n * HTML 특수문자 이스케이프 (XSS 방지)\r\n * URL이나 사용자 입력을 HTML에 삽입할 때 사용\r\n * @internal 테스트용 export\r\n */\r\nexport const escapeHtml = (str: string): string => {\r\n const htmlEscapes: Record<string, string> = {\r\n \"&\": \"&\",\r\n \"<\": \"<\",\r\n \">\": \">\",\r\n '\"': \""\",\r\n \"'\": \"'\",\r\n };\r\n return str.replace(/[&<>\"']/g, (char) => htmlEscapes[char]);\r\n};\r\n\r\n/**\r\n * 블록 배열에서 모든 이미지 URL 추출\r\n * (중첩된 children도 재귀적으로 탐색)\r\n * @internal 테스트용 export\r\n */\r\nexport const extractImageUrls = (blocks: DefaultPartialBlock[]): Set<string> => {\r\n const urls = new Set<string>();\r\n\r\n const traverse = (blockList: DefaultPartialBlock[]) => {\r\n for (const block of blockList) {\r\n // image 블록에서 URL 추출\r\n if (block.type === \"image\" && (block.props as any)?.url) {\r\n const url = (block.props as any).url;\r\n if (typeof url === \"string\" && url.trim()) {\r\n urls.add(url);\r\n }\r\n }\r\n // children이 있으면 재귀 탐색\r\n if (block.children && Array.isArray(block.children)) {\r\n traverse(block.children as DefaultPartialBlock[]);\r\n }\r\n }\r\n };\r\n\r\n traverse(blocks);\r\n return urls;\r\n};\r\n\r\n/**\r\n * 블록 배열에서 이미지·비디오(미디어) URL 추출\r\n * (중첩된 children도 재귀적으로 탐색, onImageDelete 삭제 감지용)\r\n * @internal 테스트용 export\r\n */\r\nexport const extractMediaUrls = (blocks: DefaultPartialBlock[]): Set<string> => {\r\n const urls = new Set<string>();\r\n\r\n const traverse = (blockList: DefaultPartialBlock[]) => {\r\n for (const block of blockList) {\r\n if (block.type === \"image\" && (block.props as any)?.url) {\r\n const url = (block.props as any).url;\r\n if (typeof url === \"string\" && url.trim()) urls.add(url);\r\n }\r\n if (block.type === \"video\" && (block.props as any)?.url) {\r\n const url = (block.props as any).url;\r\n if (typeof url === \"string\" && url.trim()) urls.add(url);\r\n }\r\n if (block.children && Array.isArray(block.children)) {\r\n traverse(block.children as DefaultPartialBlock[]);\r\n }\r\n }\r\n };\r\n\r\n traverse(blocks);\r\n return urls;\r\n};\r\n\r\n/**\r\n * 삭제된 미디어(이미지·비디오) URL 찾기\r\n * (이전 블록에는 있었지만 현재 블록에는 없는 URL)\r\n * @internal 테스트용 export\r\n */\r\nexport const findDeletedMediaUrls = (\r\n previousUrls: Set<string>,\r\n currentUrls: Set<string>\r\n): string[] => {\r\n const deleted: string[] = [];\r\n previousUrls.forEach((url) => {\r\n if (!currentUrls.has(url)) deleted.push(url);\r\n });\r\n return deleted;\r\n};\r\n\r\n/**\r\n * 삭제된 이미지 URL 찾기 (findDeletedMediaUrls와 동일 로직, 하위 호환용)\r\n * @internal 테스트용 export\r\n */\r\nexport const findDeletedImageUrls = (\r\n previousUrls: Set<string>,\r\n currentUrls: Set<string>\r\n): string[] => findDeletedMediaUrls(previousUrls, currentUrls);\r\n\r\nconst findBlockWithLink = (blocks: any[], targetUrl: string): any | null => {\r\n for (const block of blocks) {\r\n if (block.content) {\r\n for (const item of block.content) {\r\n if (item.type === \"link\" && item.href === targetUrl) return block;\r\n if (item.content) {\r\n for (const sub of item.content) {\r\n if (sub.type === \"link\" && sub.href === targetUrl) return block;\r\n }\r\n }\r\n }\r\n }\r\n if (block.children?.length) {\r\n const found = findBlockWithLink(block.children, targetUrl);\r\n if (found) return found;\r\n }\r\n }\r\n return null;\r\n};\r\n\r\nconst ConvertToPreviewButton = ({ url }: { url: string }) => {\r\n const editor = useBlockNoteEditor();\r\n const Components = useComponentsContext()!;\r\n\r\n return (\r\n <Components.LinkToolbar.Button\r\n className=\"bn-button\"\r\n mainTooltip=\"링크 프리뷰로 전환\"\r\n label=\"링크 프리뷰로 전환\"\r\n isSelected={false}\r\n onClick={() => {\r\n try {\r\n const allBlocks = (editor as any).document;\r\n const targetBlock = findBlockWithLink(allBlocks, url)\r\n || editor.getTextCursorPosition().block;\r\n (editor as any).replaceBlocks(\r\n [targetBlock],\r\n [{ type: \"linkPreview\", props: { url } }]\r\n );\r\n } catch (err) {\r\n console.error(\"Convert to link preview failed:\", err);\r\n }\r\n }}\r\n icon={\r\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\r\n <rect x=\"1\" y=\"3\" width=\"14\" height=\"10\" rx=\"2\" stroke=\"currentColor\" strokeWidth=\"1.5\" fill=\"none\" />\r\n <line x1=\"1\" y1=\"9\" x2=\"15\" y2=\"9\" stroke=\"currentColor\" strokeWidth=\"1.5\" />\r\n <circle cx=\"5\" cy=\"6.5\" r=\"1.5\" stroke=\"currentColor\" strokeWidth=\"1\" fill=\"none\" />\r\n </svg>\r\n }\r\n />\r\n );\r\n};\r\n\r\nconst CustomLinkToolbar = (props: any) => {\r\n const editor = useBlockNoteEditor();\r\n const Components = useComponentsContext()!;\r\n const hasLinkPreview = !!(editor as any)?._linkPreviewApiEndpoint;\r\n\r\n return (\r\n <Components.LinkToolbar.Root\r\n className=\"bn-toolbar bn-link-toolbar\"\r\n onMouseEnter={props.stopHideTimer}\r\n onMouseLeave={props.startHideTimer}\r\n >\r\n <EditLinkButton url={props.url} text={props.text} editLink={props.editLink} />\r\n <OpenLinkButton url={props.url} />\r\n <DeleteLinkButton deleteLink={props.deleteLink} />\r\n {hasLinkPreview && (\r\n <ConvertToPreviewButton url={props.url} />\r\n )}\r\n </Components.LinkToolbar.Root>\r\n );\r\n};\r\n\r\nexport default function LumirEditor({\r\n // editor options\r\n initialContent,\r\n initialEmptyBlocks = 3,\r\n uploadFile,\r\n s3Upload,\r\n tables,\r\n heading,\r\n defaultStyles = true,\r\n disableExtensions,\r\n tabBehavior = \"prefer-navigate-ui\",\r\n trailingBlock = true,\r\n allowVideoUpload = false,\r\n allowAudioUpload = false,\r\n allowFileUpload = false,\r\n maxImageFileSize,\r\n maxVideoFileSize,\r\n // link preview\r\n linkPreview,\r\n // view options\r\n editable = true,\r\n theme = \"light\",\r\n formattingToolbar = true,\r\n linkToolbar = true,\r\n sideMenu = true,\r\n emojiPicker = true,\r\n filePanel = true,\r\n tableHandles = true,\r\n onSelectionChange,\r\n className = \"\",\r\n placeholder,\r\n sideMenuAddButton = false,\r\n columnDivider = false,\r\n floatingMenu = false,\r\n floatingMenuPosition = \"sticky\",\r\n // callbacks / refs\r\n onContentChange,\r\n onError,\r\n onImageDelete,\r\n}: LumirEditorProps) {\r\n // 이미지 업로드 로딩 상태\r\n const [isUploading, setIsUploading] = useState(false);\r\n /** S3 업로드 진행률 0–100. null이면 진행률 미표시 */\r\n const [uploadProgress, setUploadProgress] = useState<number | null>(null);\r\n // 에러 상태 (사용자에게 표시할 에러 메시지)\r\n const [errorMessage, setErrorMessage] = useState<string | null>(null);\r\n // FloatingMenu: DOM에 항상 있는 file input (onchange 미발생 방지)\r\n const floatingMenuFileInputRef = useRef<HTMLInputElement | null>(null);\r\n const floatingMenuBlockRef = useRef<{ id: string } | null>(null);\r\n const floatingMenuUploadStartTimeRef = useRef<number>(0);\r\n\r\n // 에러 처리 핸들러\r\n const handleError = useCallback(\r\n (error: LumirEditorError) => {\r\n // 콜백이 있으면 호출\r\n onError?.(error);\r\n // 사용자에게 에러 메시지 표시\r\n setErrorMessage(error.getUserMessage());\r\n // 3초 후 에러 메시지 자동 숨김\r\n setTimeout(() => setErrorMessage(null), 3000);\r\n },\r\n [onError]\r\n );\r\n const validatedContent = useMemo<DefaultPartialBlock[]>(() => {\r\n // 형제 키 fontSize → styles.fontSize 복원 (직렬화 호환 레이어)\r\n return liftFontSize(\r\n ContentUtils.validateContent(initialContent, initialEmptyBlocks)\r\n );\r\n }, [initialContent, initialEmptyBlocks]);\r\n\r\n // 테이블 설정 메모이제이션\r\n const tableConfig = useMemo(() => {\r\n return EditorConfig.getDefaultTableConfig(tables);\r\n }, [\r\n tables?.splitCells,\r\n tables?.cellBackgroundColor,\r\n tables?.cellTextColor,\r\n tables?.headers,\r\n ]);\r\n\r\n // 헤딩 설정 메모이제이션\r\n const headingConfig = useMemo(() => {\r\n return EditorConfig.getDefaultHeadingConfig(heading);\r\n }, [heading?.levels?.join(\",\") ?? \"\"]);\r\n\r\n // 비활성화 확장 메모이제이션\r\n const disabledExtensions = useMemo(() => {\r\n return EditorConfig.getDisabledExtensions(\r\n disableExtensions,\r\n allowVideoUpload,\r\n allowAudioUpload,\r\n allowFileUpload\r\n );\r\n }, [disableExtensions, allowVideoUpload, allowAudioUpload, allowFileUpload]);\r\n\r\n // #region agent log\r\n useEffect(() => {\r\n DEBUG_LOG(\"LumirEditor:init:disabledExtensions\", \"snapshot\", {\r\n allowVideoUpload,\r\n hasVideoInDisabled: disabledExtensions.includes(\"video\"),\r\n disabledList: disabledExtensions.slice(0, 15),\r\n });\r\n }, [allowVideoUpload, disabledExtensions]);\r\n // #endregion\r\n\r\n // fileNameTransform 콜백을 ref로 관리 (에디터 재생성 방지)\r\n const fileNameTransformRef = useRef(s3Upload?.fileNameTransform);\r\n useEffect(() => {\r\n fileNameTransformRef.current = s3Upload?.fileNameTransform;\r\n }, [s3Upload?.fileNameTransform]);\r\n\r\n // S3 업로드 설정 메모이제이션 (객체 참조 안정화)\r\n // 주의: fileNameTransform은 ref로 관리하므로 의존성에서 제외\r\n const memoizedS3Upload = useMemo(() => {\r\n if (!s3Upload) return undefined;\r\n return {\r\n apiEndpoint: s3Upload.apiEndpoint,\r\n env: s3Upload.env,\r\n path: s3Upload.path,\r\n appendUUID: s3Upload.appendUUID,\r\n preserveExtension: s3Upload.preserveExtension,\r\n uploadTimeoutMs: s3Upload.uploadTimeoutMs,\r\n maxRetries: s3Upload.maxRetries,\r\n onProgress: (percent: number) => {\r\n setUploadProgress(percent);\r\n s3Upload.onProgress?.(percent);\r\n },\r\n // 최신 콜백을 항상 사용하도록 ref를 통해 접근\r\n fileNameTransform: ((originalName: string, file: File) => {\r\n return fileNameTransformRef.current\r\n ? fileNameTransformRef.current(originalName, file)\r\n : originalName;\r\n }) as ((originalName: string, file: File) => string) | undefined,\r\n };\r\n }, [\r\n s3Upload?.apiEndpoint,\r\n s3Upload?.env,\r\n s3Upload?.path,\r\n s3Upload?.appendUUID,\r\n s3Upload?.preserveExtension,\r\n s3Upload?.uploadTimeoutMs,\r\n s3Upload?.maxRetries,\r\n s3Upload?.onProgress,\r\n ]);\r\n\r\n const editor = useCreateBlockNote(\r\n {\r\n // HTML 미리보기 블록이 포함된 커스텀 스키마 사용\r\n schema,\r\n // 모든 BlockNote UI 텍스트(테이블 드롭다운 등) 한글 적용 + \"색깔\"→\"색\"\r\n dictionary: {\r\n ...ko,\r\n drag_handle: { ...ko.drag_handle, colors_menuitem: \"색\" },\r\n formatting_toolbar: {\r\n ...ko.formatting_toolbar,\r\n colors: { ...ko.formatting_toolbar.colors, tooltip: \"색\" },\r\n },\r\n },\r\n initialContent: validatedContent as any,\r\n tables: tableConfig,\r\n heading: headingConfig,\r\n animations: false, // 기본적으로 애니메이션 비활성화\r\n defaultStyles,\r\n // 확장 비활성: 비디오/오디오/파일 제어\r\n disableExtensions: disabledExtensions,\r\n _tiptapOptions: {\r\n extensions: [\r\n VerticalAlignmentExtension,\r\n // 행 높이 attr 등록은 항상(저장된 높이 렌더), 드래그 리사이즈 UI는\r\n // tableHandles prop으로 게이트(기존 grip 컨트롤러와 동일 게이트).\r\n RowHeightExtension.configure({ resizable: tableHandles }),\r\n // 표 블록 정렬(좌/가운데/우) attr.\r\n TableAlignmentExtension,\r\n // 셀 포커스 시 Ctrl/Cmd+A → 표 전체 선택.\r\n TableSelectAllExtension,\r\n ],\r\n },\r\n placeholders: placeholder\r\n ? { default: placeholder, emptyDocument: placeholder }\r\n : undefined,\r\n tabBehavior,\r\n trailingBlock,\r\n uploadFile: async (file) => {\r\n const allowedImage = isImageFile(file, maxImageFileSize);\r\n const allowedVideo =\r\n allowVideoUpload && isVideoFile(file, maxVideoFileSize);\r\n // #region agent log\r\n DEBUG_LOG(\"uploadFile:step1:entry\", \"editor uploadFile callback invoked\", {\r\n fileName: file.name,\r\n fileType: file.type,\r\n fileSize: file.size,\r\n allowVideoUpload,\r\n allowedImage,\r\n allowedVideo,\r\n });\r\n // #endregion\r\n if (!allowedImage && !allowedVideo) {\r\n const error = LumirEditorError.invalidFileType(\r\n file.name,\r\n allowVideoUpload\r\n );\r\n handleError(error);\r\n throw error;\r\n }\r\n\r\n try {\r\n setUploadProgress(0);\r\n let fileUrl: string;\r\n // #region agent log\r\n const branch = uploadFile ? \"custom\" : memoizedS3Upload?.apiEndpoint ? \"s3\" : \"none\";\r\n DEBUG_LOG(\"uploadFile:step2:branch\", \"upload path\", {\r\n branch,\r\n hasCustomUploadFile: !!uploadFile,\r\n hasS3ApiEndpoint: !!memoizedS3Upload?.apiEndpoint,\r\n });\r\n // #endregion\r\n // 1. 사용자 정의 uploadFile 우선\r\n if (uploadFile) {\r\n const t0 = Date.now();\r\n DEBUG_LOG(\"uploadFile:step3a:custom\", \"calling custom uploadFile\", { fileName: file.name });\r\n fileUrl = await uploadFile(file);\r\n DEBUG_LOG(\"uploadFile:step3a:done\", \"custom uploadFile returned\", { urlLen: fileUrl?.length, elapsedMs: Date.now() - t0 });\r\n }\r\n // 2. S3 업로드 (uploadFile 없을 때)\r\n else if (memoizedS3Upload?.apiEndpoint) {\r\n const t0 = Date.now();\r\n DEBUG_LOG(\"uploadFile:step3b:s3\", \"calling S3 uploader\", { fileName: file.name });\r\n const s3Uploader = createS3Uploader(memoizedS3Upload);\r\n fileUrl = await s3Uploader(file);\r\n DEBUG_LOG(\"uploadFile:step3b:done\", \"S3 uploader returned\", { urlLen: fileUrl?.length, elapsedMs: Date.now() - t0 });\r\n }\r\n // 3. 업로드 방법이 없으면 에러\r\n else {\r\n const error = LumirEditorError.s3ConfigError(\r\n \"No upload method available. Please provide uploadFile or s3Upload configuration.\"\r\n );\r\n handleError(error);\r\n throw error;\r\n }\r\n\r\n // BlockNote가 파일 타입에 따라 이미지/비디오 블록을 생성하도록 URL만 반환\r\n // #region agent log\r\n DEBUG_LOG(\"uploadFile:step4:success\", \"returning URL\", {\r\n fileName: file.name,\r\n urlPrefix: fileUrl.slice(0, 80),\r\n });\r\n // #endregion\r\n return fileUrl;\r\n } catch (error) {\r\n // #region agent log\r\n DEBUG_LOG(\"uploadFile:step5:catch\", \"uploadFile threw\", {\r\n fileName: file.name,\r\n errorMessage: error instanceof Error ? error.message : String(error),\r\n });\r\n // #endregion\r\n // 이미 LumirEditorError인 경우 다시 처리하지 않음\r\n if (error instanceof LumirEditorError) {\r\n throw error;\r\n }\r\n const lumirError = LumirEditorError.uploadFailed(\r\n error instanceof Error ? error.message : String(error),\r\n error instanceof Error ? error : undefined\r\n );\r\n handleError(lumirError);\r\n throw lumirError;\r\n } finally {\r\n setUploadProgress(null);\r\n }\r\n },\r\n pasteHandler: (ctx) => {\r\n const { event, editor, defaultPasteHandler } = ctx as any;\r\n\r\n // URL 붙여넣기 감지 → linkPreview 블록 생성\r\n if (linkPreview?.apiEndpoint) {\r\n const text = event?.clipboardData?.getData?.(\"text/plain\") || \"\";\r\n const trimmed = text.trim();\r\n if (\r\n trimmed &&\r\n /^https?:\\/\\/\\S+$/i.test(trimmed) &&\r\n !event?.clipboardData?.files?.length\r\n ) {\r\n event.preventDefault();\r\n const currentBlock = editor.getTextCursorPosition().block;\r\n const blockText = currentBlock.content\r\n ?.map((c: any) => c.text || \"\")\r\n .join(\"\")\r\n .trim();\r\n if (!blockText && currentBlock.type === \"paragraph\") {\r\n editor.updateBlock(currentBlock, {\r\n type: \"linkPreview\",\r\n props: { url: trimmed },\r\n });\r\n } else {\r\n editor.insertBlocks(\r\n [{ type: \"linkPreview\", props: { url: trimmed } }],\r\n currentBlock,\r\n \"after\"\r\n );\r\n }\r\n return true;\r\n }\r\n }\r\n\r\n // 엑셀/스프레드시트 표 붙여넣기 감지\r\n // 엑셀은 클립보드에 표 HTML과 함께 셀 비트맵(image.png)을 올리므로,\r\n // 아래 이미지 업로드 분기가 가로채기 전에 HTML 표를 우선 처리한다.\r\n // BlockNote 기본 HTML 파서가 병합/들쑥날쑥 행을 안전하게 표로 변환한다.\r\n const pastedHtml =\r\n event?.clipboardData?.getData?.(\"text/html\") || \"\";\r\n if (/<table[\\s>]/i.test(pastedHtml)) {\r\n // #region agent log\r\n DEBUG_LOG(\"paste:step0:table\", \"table HTML detected, using pasteHTML\", {\r\n htmlLen: pastedHtml.length,\r\n hasFiles: !!event?.clipboardData?.files?.length,\r\n });\r\n // #endregion\r\n event.preventDefault();\r\n // 에디터 너비보다 넓은 표는 비율 유지하며 축소(가로 스크롤 방지):\r\n // 붙여넣기 전에 HTML에서 열 너비를 읽어 maxWidth에 맞춘 columnWidths를 계산하고,\r\n // 붙여넣은 뒤 새 표 블록에 적용한다(파서가 셀 width를 colwidth로 읽지 않으므로).\r\n const pmDom = (editor as any).prosemirrorView?.dom as HTMLElement | undefined;\r\n const maxWidth = pmDom?.clientWidth ? pmDom.clientWidth - 8 : 0;\r\n const fittedWidths = computeFittedColumnWidthsPerTable(pastedHtml, maxWidth);\r\n const beforeTableIds = new Set<string>(\r\n collectTableBlocks(editor.document).map((b: any) => b.id),\r\n );\r\n // 엑셀 CSS 서식(색/정렬/굵게)을 BlockNote가 인식하는 형태로 정규화 후 삽입.\r\n editor.pasteHTML(normalizeExcelTableHtml(pastedHtml));\r\n applyFittedWidthsToNewTables(editor, beforeTableIds, fittedWidths);\r\n return true;\r\n }\r\n\r\n const fileList =\r\n (event?.clipboardData?.files as FileList | null) ?? null;\r\n const files: File[] = fileList ? Array.from(fileList) : [];\r\n const acceptedFiles: File[] = files.filter(\r\n (f) =>\r\n isImageFile(f, maxImageFileSize) || (allowVideoUpload && isVideoFile(f, maxVideoFileSize))\r\n );\r\n // #region agent log\r\n DEBUG_LOG(\"paste:step1:files\", \"paste clipboard files\", {\r\n filesCount: files.length,\r\n acceptedCount: acceptedFiles.length,\r\n fileNames: files.map((f) => f.name),\r\n acceptedNames: acceptedFiles.map((f) => f.name),\r\n });\r\n // #endregion\r\n // 파일이 있지만 허용된 미디어가 없으면 기본 처리 막고 무시\r\n if (files.length > 0 && acceptedFiles.length === 0) {\r\n event.preventDefault();\r\n return true;\r\n }\r\n\r\n if (acceptedFiles.length === 0) {\r\n return defaultPasteHandler() ?? false;\r\n }\r\n\r\n event.preventDefault();\r\n (async () => {\r\n setIsUploading(true);\r\n try {\r\n for (const file of acceptedFiles) {\r\n try {\r\n // #region agent log\r\n DEBUG_LOG(\"paste:step2:upload\", \"calling uploadFile for paste\", {\r\n fileName: file.name,\r\n fileType: file.type,\r\n });\r\n // #endregion\r\n const url = await editor.uploadFile(file);\r\n if (isImageFile(file, maxImageFileSize)) {\r\n editor.pasteHTML(\r\n `<img src=\"${escapeHtml(url)}\" alt=\"image\" />`\r\n );\r\n } else if (isVideoFile(file, maxVideoFileSize)) {\r\n const currentBlock = editor.getTextCursorPosition().block;\r\n editor.insertBlocks(\r\n [{ type: \"video\", props: { url } }] as any,\r\n currentBlock,\r\n \"after\"\r\n );\r\n }\r\n } catch (err) {\r\n console.warn(\r\n \"Upload failed, skipped:\",\r\n file.name || \"\",\r\n err\r\n );\r\n }\r\n }\r\n } finally {\r\n setIsUploading(false);\r\n }\r\n })();\r\n return true;\r\n },\r\n },\r\n [\r\n validatedContent,\r\n tableConfig,\r\n headingConfig,\r\n defaultStyles,\r\n disabledExtensions,\r\n tabBehavior,\r\n trailingBlock,\r\n uploadFile,\r\n memoizedS3Upload,\r\n allowVideoUpload,\r\n linkPreview?.apiEndpoint,\r\n placeholder,\r\n // tableHandles 변경 시 RowHeightExtension의 resizable 게이트가 반영되도록 재생성\r\n tableHandles,\r\n ]\r\n );\r\n\r\n // Link Preview API endpoint를 에디터 인스턴스에 동기적으로 연결\r\n // (useEffect 사용 시 블록 렌더링보다 늦게 설정되어 첫 fetch가 실패할 수 있음)\r\n if (editor && linkPreview?.apiEndpoint) {\r\n (editor as any)._linkPreviewApiEndpoint = linkPreview.apiEndpoint;\r\n }\r\n\r\n // columnDnd 플러그인이 드롭 시 BlockNote 블록 API(getBlock/replaceBlocks)에 접근할 수\r\n // 있도록, ProseMirror view에 에디터 참조를 심는다(플러그인은 view.__lumirEditor로 읽음).\r\n if (editor) {\r\n try {\r\n const view = (editor as any).prosemirrorView;\r\n if (view) {\r\n view.__lumirEditor = editor;\r\n }\r\n } catch {\r\n /* view 미준비 시 무시(다음 렌더에서 설정) */\r\n }\r\n }\r\n\r\n // 편집 가능 여부 설정\r\n useEffect(() => {\r\n if (editor) {\r\n editor.isEditable = editable;\r\n }\r\n }, [editor, editable]);\r\n\r\n // 행/열 삭제 결정적 처리 래퍼.\r\n // 코어의 tableHandles.removeRowOrColumn은 삭제 대상 표를 플러그인 내부 상태\r\n // view.tablePos로 찾는데, 포커스 기반 커스텀 그립 컨트롤러에서는 이 값이 어긋날 수\r\n // 있어 잘못된 CellSelection이 만들어지고 첫 열/행 삭제가 화면에 반영되지 않는다.\r\n // 현재 selection(커서)이 위치한 실제 표 기준으로 직접 삭제하고, 표 컨텍스트가\r\n // 아니면(=false) 코어 기본 동작으로 폴백한다.\r\n useEffect(() => {\r\n if (!editor) return;\r\n const th: any = (editor as any).tableHandles;\r\n if (!th || th.__lumirColDelPatched || typeof th.removeRowOrColumn !== \"function\")\r\n return;\r\n const orig = th.removeRowOrColumn.bind(th);\r\n th.removeRowOrColumn = (index: number, dir: \"row\" | \"column\") => {\r\n if (removeFocusedRowOrColumn(editor, index, dir)) return;\r\n return orig(index, dir);\r\n };\r\n th.__lumirColDelPatched = true;\r\n }, [editor]);\r\n\r\n // floatingMenu(상단 고정 툴바) 사용 시: 일반 텍스트 선택에서는 팝업 포매팅\r\n // 툴바를 띄우지 않는다 (중복 UI + 상단 툴바에서 스타일 적용 직후 팝업이\r\n // (0,0) 위치로 잘못 재표시되는 문제 방지). 테이블 셀·노드 선택은 팝업 유지.\r\n // show:true emit 직후 동기적으로 closeMenu()하면 React 배칭으로 화면에\r\n // 그려지기 전에 show:false로 정리된다.\r\n useEffect(() => {\r\n if (!editor || !floatingMenu) return;\r\n\r\n const ft = (editor as any).formattingToolbar;\r\n if (!ft?.onUpdate) return;\r\n\r\n const unsubscribe = ft.onUpdate((state: { show?: boolean }) => {\r\n if (!state?.show) return;\r\n const selection = (editor as any)._tiptapEditor?.state?.selection;\r\n if (isSuppressiblePopupSelection(editor, selection)) {\r\n ft.closeMenu();\r\n }\r\n });\r\n return unsubscribe;\r\n }, [editor, floatingMenu]);\r\n\r\n // 콘텐츠 변경 감지\r\n useEffect(() => {\r\n if (!editor || !onContentChange) return;\r\n\r\n const handleContentChange = () => {\r\n const blocks = editor.topLevelBlocks as DefaultPartialBlock[];\r\n // ⚠️ styles.fontSize는 구버전 SDK(fontSize 스펙 없음)를 크래시시키므로\r\n // 외부로 나가는 JSON은 반드시 lowerFontSize(형제 키 변환)를 거친다.\r\n // 표 커스텀 셀 attr(세로 정렬·행 높이)은 한 번의 순회로 함께 주입한다.\r\n const patched = lowerFontSize(\r\n injectTableBlockAttrs(\r\n injectTableCellAttrs(blocks, editor, [\r\n \"verticalAlignment\",\r\n \"rowHeight\",\r\n ]),\r\n editor\r\n )\r\n );\r\n onContentChange(patched);\r\n };\r\n\r\n return editor.onEditorContentChange(handleContentChange);\r\n }, [editor, onContentChange]);\r\n\r\n // 이미지·비디오 삭제 감지 (onImageDelete 콜백 — 미디어 URL 모두 전달)\r\n const previousMediaUrlsRef = useRef<Set<string>>(new Set());\r\n\r\n useEffect(() => {\r\n if (!editor) return;\r\n\r\n const initialBlocks = editor.topLevelBlocks as DefaultPartialBlock[];\r\n previousMediaUrlsRef.current = extractMediaUrls(initialBlocks);\r\n }, [editor]);\r\n\r\n useEffect(() => {\r\n if (!editor || !onImageDelete) return;\r\n\r\n const handleMediaDeleteCheck = () => {\r\n const currentBlocks = editor.topLevelBlocks as DefaultPartialBlock[];\r\n const currentUrls = extractMediaUrls(currentBlocks);\r\n const previousUrls = previousMediaUrlsRef.current;\r\n\r\n const deletedUrls = findDeletedMediaUrls(previousUrls, currentUrls);\r\n deletedUrls.forEach((url) => {\r\n onImageDelete(url);\r\n });\r\n\r\n previousMediaUrlsRef.current = currentUrls;\r\n };\r\n\r\n return editor.onEditorContentChange(handleMediaDeleteCheck);\r\n }, [editor, onImageDelete]);\r\n\r\n // 드래그앤드롭 이미지/HTML 처리\r\n useEffect(() => {\r\n const el = editor?.domElement as HTMLElement | undefined;\r\n if (!el) return;\r\n\r\n const handleDragOver = (e: DragEvent) => {\r\n if (e.defaultPrevented) return;\r\n const hasFiles = (\r\n e.dataTransfer?.types as unknown as string[] | undefined\r\n )?.includes?.(\"Files\");\r\n if (hasFiles) {\r\n e.preventDefault();\r\n e.stopPropagation();\r\n }\r\n };\r\n\r\n const handleDrop = (e: DragEvent) => {\r\n if (!e.dataTransfer) return;\r\n const hasFiles = (\r\n (e.dataTransfer.types as unknown as string[] | undefined) ?? []\r\n ).includes(\"Files\");\r\n if (!hasFiles) return;\r\n\r\n e.preventDefault();\r\n e.stopPropagation();\r\n\r\n const items = Array.from(e.dataTransfer.items ?? []);\r\n const files = items\r\n .filter((it) => it.kind === \"file\")\r\n .map((it) => it.getAsFile())\r\n .filter((f): f is File => !!f);\r\n\r\n // 이미지, 동영상, HTML 파일 분리\r\n const imageFiles = files.filter((f) => isImageFile(f, maxImageFileSize));\r\n const videoFiles = allowVideoUpload\r\n ? files.filter((f) => isVideoFile(f, maxVideoFileSize))\r\n : [];\r\n const htmlFiles = files.filter(isHtmlFile);\r\n\r\n // #region agent log\r\n DEBUG_LOG(\"drop:step1:files\", \"drop received\", {\r\n filesCount: files.length,\r\n imageCount: imageFiles.length,\r\n videoCount: videoFiles.length,\r\n htmlCount: htmlFiles.length,\r\n allowVideoUpload,\r\n firstFile: files[0]\r\n ? {\r\n name: files[0].name,\r\n type: files[0].type,\r\n size: files[0].size,\r\n isImage: isImageFile(files[0], maxImageFileSize),\r\n isVideo: isVideoFile(files[0], maxVideoFileSize),\r\n }\r\n : null,\r\n });\r\n // #endregion\r\n\r\n if (\r\n imageFiles.length === 0 &&\r\n htmlFiles.length === 0 &&\r\n videoFiles.length === 0\r\n )\r\n return;\r\n\r\n (async () => {\r\n setIsUploading(true);\r\n try {\r\n // #region agent log\r\n DEBUG_LOG(\"drop:step2:async\", \"drop async started\", {\r\n imageCount: imageFiles.length,\r\n videoCount: videoFiles.length,\r\n });\r\n // #endregion\r\n // 이미지 파일 처리\r\n for (const file of imageFiles) {\r\n try {\r\n if (editor?.uploadFile) {\r\n const url = await editor.uploadFile(file);\r\n if (url && typeof url === \"string\") {\r\n editor.pasteHTML(\r\n `<img src=\"${escapeHtml(url)}\" alt=\"image\" />`\r\n );\r\n }\r\n }\r\n } catch (err) {\r\n console.warn(\r\n \"Image upload failed, skipped:\",\r\n file.name || \"\",\r\n err\r\n );\r\n }\r\n }\r\n\r\n // 동영상 파일 처리 - video 블록으로 삽입\r\n // #region agent log\r\n DEBUG_LOG(\"drop:step3:videoLoop\", \"video loop start\", {\r\n videoCount: videoFiles.length,\r\n names: videoFiles.map((f) => f.name),\r\n });\r\n // #endregion\r\n for (const file of videoFiles) {\r\n try {\r\n if (editor?.uploadFile) {\r\n // #region agent log\r\n DEBUG_LOG(\"drop:step4:videoUpload\", \"calling uploadFile for video\", {\r\n fileName: file.name,\r\n });\r\n // #endregion\r\n const url = await editor.uploadFile(file);\r\n if (url && typeof url === \"string\") {\r\n const currentBlock = editor.getTextCursorPosition().block;\r\n editor.insertBlocks(\r\n [{ type: \"video\", props: { url } }] as any,\r\n currentBlock,\r\n \"after\"\r\n );\r\n }\r\n }\r\n } catch (err) {\r\n console.warn(\r\n \"Video upload failed, skipped:\",\r\n file.name || \"\",\r\n err\r\n );\r\n }\r\n }\r\n\r\n // HTML 파일 처리 - htmlPreview 블록으로 삽입\r\n for (const file of htmlFiles) {\r\n try {\r\n const htmlContent = await file.text();\r\n const currentBlock = editor.getTextCursorPosition().block;\r\n\r\n // htmlPreview 블록 삽입\r\n editor.insertBlocks(\r\n [\r\n {\r\n type: \"htmlPreview\",\r\n props: {\r\n htmlContent: htmlContent,\r\n fileName: file.name,\r\n height: \"400px\",\r\n },\r\n },\r\n ],\r\n currentBlock,\r\n \"after\"\r\n );\r\n } catch (err) {\r\n console.warn(\r\n \"HTML file processing failed, skipped:\",\r\n file.name || \"\",\r\n err\r\n );\r\n }\r\n }\r\n } finally {\r\n setIsUploading(false);\r\n }\r\n })();\r\n };\r\n\r\n el.addEventListener(\"dragover\", handleDragOver, { capture: true });\r\n el.addEventListener(\"drop\", handleDrop, { capture: true });\r\n\r\n return () => {\r\n el.removeEventListener(\"dragover\", handleDragOver, {\r\n capture: true,\r\n } as any);\r\n el.removeEventListener(\"drop\", handleDrop, { capture: true } as any);\r\n };\r\n }, [editor, allowVideoUpload]);\r\n\r\n // SideMenu 설정 (Add 버튼 제어)\r\n const computedSideMenu = useMemo(() => {\r\n return sideMenuAddButton ? sideMenu : false;\r\n }, [sideMenuAddButton, sideMenu]);\r\n\r\n // Add 버튼 없는 사이드 메뉴 (드래그 핸들만) - 메모이제이션\r\n // 드래그 핸들 드롭다운에 표 정렬(좌/가운데/우) 항목을 추가한 커스텀 메뉴 사용.\r\n const DragHandleOnlySideMenu = useMemo(() => {\r\n return (props: any) => (\r\n <BlockSideMenu {...props}>\r\n <DragHandleButton {...props} dragHandleMenu={LumirDragHandleMenu} />\r\n </BlockSideMenu>\r\n );\r\n }, []);\r\n\r\n return (\r\n <div\r\n className={cn(\r\n \"lumirEditor\",\r\n columnDivider && \"lumir-column-divider\",\r\n className,\r\n )}\r\n style={{ position: \"relative\", display: \"flex\", flexDirection: \"column\" }}\r\n >\r\n {/* FloatingMenu를 BlockNoteView 외부로 이동 */}\r\n {floatingMenu && editor && (\r\n <>\r\n <input\r\n ref={floatingMenuFileInputRef}\r\n type=\"file\"\r\n accept={\r\n allowVideoUpload\r\n ? \"image/*,video/mp4,video/webm,video/ogg,video/quicktime,.mov\"\r\n : \"image/*\"\r\n }\r\n style={{\r\n position: \"absolute\",\r\n left: \"-9999px\",\r\n opacity: 0,\r\n pointerEvents: \"none\",\r\n }}\r\n onChange={async (e) => {\r\n const inputEl = e.target as HTMLInputElement;\r\n const file = inputEl.files?.[0];\r\n // #region agent log\r\n DEBUG_LOG(\"FloatingMenu:step3:onchange\", \"file input onchange fired\", {\r\n hasFile: !!file,\r\n fileName: file?.name,\r\n fileType: file?.type,\r\n fileSize: file?.size,\r\n hasUploadFile: !!editor?.uploadFile,\r\n });\r\n // #endregion\r\n const blockToInsertAfter = floatingMenuBlockRef.current;\r\n if (file && editor.uploadFile && blockToInsertAfter) {\r\n const allowedImage = isImageFile(file, maxImageFileSize);\r\n const allowedVideo = allowVideoUpload && isVideoFile(file, maxVideoFileSize);\r\n // #region agent log\r\n DEBUG_LOG(\"FloatingMenu:step4:fileCheck\", \"allowed check\", {\r\n fileName: file.name,\r\n allowedImage,\r\n allowedVideo,\r\n });\r\n // #endregion\r\n if (allowedImage || allowedVideo) {\r\n try {\r\n setIsUploading(true);\r\n floatingMenuUploadStartTimeRef.current = Date.now();\r\n // #region agent log\r\n DEBUG_LOG(\"FloatingMenu:step5:uploadStart\", \"calling editor.uploadFile\", {\r\n fileName: file.name,\r\n });\r\n // #endregion\r\n const url = await editor.uploadFile(file);\r\n const blockType = allowedVideo ? \"video\" : \"image\";\r\n const elapsedMs = Date.now() - floatingMenuUploadStartTimeRef.current;\r\n // #region agent log\r\n DEBUG_LOG(\"FloatingMenu:step6:uploadDone\", \"upload returned, inserting block\", {\r\n blockType,\r\n blockId: blockToInsertAfter.id,\r\n urlLen: url?.length,\r\n elapsedMs,\r\n });\r\n // #endregion\r\n editor.insertBlocks(\r\n [\r\n {\r\n type: blockType,\r\n props: { url: url as string },\r\n },\r\n ] as any,\r\n blockToInsertAfter,\r\n \"after\"\r\n );\r\n } catch (err) {\r\n // #region agent log\r\n DEBUG_LOG(\"FloatingMenu:step7:catch\", \"upload or insert failed\", {\r\n errMsg: err instanceof Error ? err.message : String(err),\r\n });\r\n // #endregion\r\n console.error(\"Upload failed:\", err);\r\n } finally {\r\n setIsUploading(false);\r\n }\r\n }\r\n }\r\n inputEl.value = \"\";\r\n }}\r\n />\r\n <FloatingMenu\r\n editor={editor as any}\r\n position={floatingMenuPosition}\r\n onImageUpload={() => {\r\n // #region agent log\r\n DEBUG_LOG(\"FloatingMenu:step1:click\", \"upload button clicked\", {\r\n allowVideoUpload,\r\n });\r\n // #endregion\r\n let blockToInsertAfter: { id: string };\r\n try {\r\n blockToInsertAfter = editor.getTextCursorPosition().block;\r\n } catch (err) {\r\n DEBUG_LOG(\"FloatingMenu:step1b:error\", \"getTextCursorPosition failed\", {\r\n err: err instanceof Error ? err.message : String(err),\r\n });\r\n return;\r\n }\r\n floatingMenuBlockRef.current = blockToInsertAfter;\r\n const input = floatingMenuFileInputRef.current;\r\n if (!input) return;\r\n input.accept = allowVideoUpload\r\n ? \"image/*,video/mp4,video/webm,video/ogg,video/quicktime,.mov\"\r\n : \"image/*\";\r\n input.value = \"\";\r\n // #region agent log\r\n DEBUG_LOG(\"FloatingMenu:step2:inputReady\", \"persistent input ref, about to click\", {\r\n accept: input.accept,\r\n });\r\n DEBUG_LOG(\"FloatingMenu:step2b:click\", \"input.click() about to be called\", {});\r\n // #endregion\r\n input.click();\r\n }}\r\n />\r\n </>\r\n )}\r\n <BlockNoteView\r\n editor={editor}\r\n editable={editable}\r\n theme={theme}\r\n formattingToolbar={false}\r\n linkToolbar={false}\r\n sideMenu={computedSideMenu}\r\n slashMenu={false}\r\n emojiPicker={emojiPicker}\r\n filePanel={filePanel}\r\n tableHandles={false}\r\n onSelectionChange={onSelectionChange}\r\n >\r\n {/* Notion 스타일 focus 기반 테이블 grip (기본 hover 핸들 대체).\r\n 소비자 tableHandles prop을 게이트로 유지. */}\r\n {tableHandles && <LumirTableHandlesController />}\r\n {formattingToolbar && (\r\n <FormattingToolbarController\r\n formattingToolbar={CustomFormattingToolbar}\r\n />\r\n )}\r\n {linkToolbar && (\r\n linkPreview?.apiEndpoint\r\n ? <LinkToolbarController linkToolbar={CustomLinkToolbar} />\r\n : <LinkToolbarController />\r\n )}\r\n {\r\n <SuggestionMenuController\r\n triggerCharacter=\"/\"\r\n getItems={useCallback(\r\n async (query: string) => {\r\n const items = getDefaultReactSlashMenuItems(editor);\r\n // 오디오/파일 항목 제거; 비디오는 allowVideoUpload일 때만 표시\r\n const filtered = items.filter((item: any) => {\r\n const key = (item?.key || \"\").toString().toLowerCase();\r\n const title = (item?.title || \"\").toString().toLowerCase();\r\n if (key === \"video\" || title.includes(\"video\"))\r\n return allowVideoUpload;\r\n if ([\"audio\", \"file\"].includes(key)) return false;\r\n if (title.includes(\"audio\") || title.includes(\"file\"))\r\n return false;\r\n return true;\r\n });\r\n\r\n // HTML 미리보기 슬래시 메뉴 항목 추가\r\n const htmlPreviewItem = {\r\n title: \"HTML Preview\",\r\n onItemClick: () => {\r\n // 파일 선택 다이얼로그 열기\r\n const input = document.createElement(\"input\");\r\n input.type = \"file\";\r\n input.accept = \".html,.htm\";\r\n input.onchange = async (e) => {\r\n const file = (e.target as HTMLInputElement).files?.[0];\r\n if (file) {\r\n const htmlContent = await file.text();\r\n const currentBlock =\r\n editor.getTextCursorPosition().block;\r\n editor.insertBlocks(\r\n [\r\n {\r\n type: \"htmlPreview\",\r\n props: {\r\n htmlContent: htmlContent,\r\n fileName: file.name,\r\n height: \"400px\",\r\n },\r\n },\r\n ],\r\n currentBlock,\r\n \"after\"\r\n );\r\n }\r\n };\r\n input.click();\r\n },\r\n aliases: [\"html\", \"preview\", \"웹\", \"웹페이지\"],\r\n group: \"Embeds\",\r\n icon: (\r\n <svg\r\n width=\"18\"\r\n height=\"18\"\r\n viewBox=\"0 0 24 24\"\r\n fill=\"none\"\r\n stroke=\"currentColor\"\r\n strokeWidth=\"2\"\r\n strokeLinecap=\"round\"\r\n strokeLinejoin=\"round\"\r\n >\r\n <polyline points=\"16 18 22 12 16 6\"></polyline>\r\n <polyline points=\"8 6 2 12 8 18\"></polyline>\r\n </svg>\r\n ),\r\n subtext: \"HTML 파일을 미리보기로 삽입\",\r\n };\r\n\r\n // 2단 컬럼(다단 레이아웃) 슬래시 항목 — 생성 시 구분선 유무를 선택해 고정.\r\n // columnList/column은 public blockSchema에 없어 insertBlocks가 거부 →\r\n // PM 트랜잭션으로 직접 삽입(insertTwoColumns).\r\n const columnIcon = (withDivider: boolean) => (\r\n <svg\r\n width=\"18\"\r\n height=\"18\"\r\n viewBox=\"0 0 24 24\"\r\n fill=\"none\"\r\n stroke=\"currentColor\"\r\n strokeWidth=\"2\"\r\n strokeLinecap=\"round\"\r\n strokeLinejoin=\"round\"\r\n >\r\n <rect x=\"3\" y=\"4\" width=\"7\" height=\"16\" rx=\"1\" />\r\n <rect x=\"14\" y=\"4\" width=\"7\" height=\"16\" rx=\"1\" />\r\n {withDivider && (\r\n <line x1=\"12\" y1=\"3\" x2=\"12\" y2=\"21\" strokeDasharray=\"2 2\" />\r\n )}\r\n </svg>\r\n );\r\n const columnItem = {\r\n title: \"2단 컬럼\",\r\n onItemClick: () => insertTwoColumns(editor, false),\r\n aliases: [\"columns\", \"column\", \"2col\", \"단\", \"컬럼\", \"다단\", \"분할\"],\r\n group: \"Basic blocks\",\r\n icon: columnIcon(false),\r\n subtext: \"블록을 좌우 2단으로 배치\",\r\n };\r\n const columnDividerItem = {\r\n title: \"2단 컬럼 (구분선)\",\r\n onItemClick: () => insertTwoColumns(editor, true),\r\n aliases: [\r\n \"columns divider\",\r\n \"구분선\",\r\n \"컬럼 구분선\",\r\n \"2단 구분선\",\r\n \"divider\",\r\n ],\r\n group: \"Basic blocks\",\r\n icon: columnIcon(true),\r\n subtext: \"가운데 세로 구분선이 있는 2단 컬럼\",\r\n };\r\n\r\n const allItems = [\r\n ...filtered,\r\n htmlPreviewItem,\r\n columnItem,\r\n columnDividerItem,\r\n ];\r\n\r\n // Link Preview 슬래시 메뉴 항목 (linkPreview 설정이 있을 때만)\r\n if (linkPreview?.apiEndpoint) {\r\n allItems.push({\r\n title: \"Link Preview\",\r\n onItemClick: () => {\r\n insertOrUpdateBlock(editor, {\r\n type: \"linkPreview\",\r\n props: { url: \"\" },\r\n });\r\n },\r\n aliases: [\r\n \"link\",\r\n \"preview\",\r\n \"url\",\r\n \"링크\",\r\n \"미리보기\",\r\n \"프리뷰\",\r\n ],\r\n group: \"Embeds\",\r\n icon: (\r\n <svg\r\n width=\"18\"\r\n height=\"18\"\r\n viewBox=\"0 0 24 24\"\r\n fill=\"none\"\r\n stroke=\"currentColor\"\r\n strokeWidth=\"2\"\r\n strokeLinecap=\"round\"\r\n strokeLinejoin=\"round\"\r\n >\r\n <path d=\"M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71\" />\r\n <path d=\"M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71\" />\r\n </svg>\r\n ),\r\n subtext: \"URL의 미리보기 카드를 삽입\",\r\n });\r\n }\r\n\r\n // 한글 제목은 유지하되, 영어로도 검색 가능하도록 각 항목에 영어\r\n // 로케일(en)의 별칭 + 영어 제목을 별칭으로 병합한다(예: /table, /heading).\r\n const enSlash = en.slash_menu as Record<\r\n string,\r\n { title?: string; aliases?: string[] }\r\n >;\r\n for (const it of allItems as any[]) {\r\n const enEntry = it.key ? enSlash[it.key] : undefined;\r\n if (!enEntry) continue;\r\n const extra = [...(enEntry.aliases ?? []), enEntry.title]\r\n .filter((s): s is string => Boolean(s))\r\n .map((s) => s.toLowerCase());\r\n it.aliases = Array.from(\r\n new Set([...(it.aliases ?? []), ...extra])\r\n );\r\n }\r\n\r\n // 같은 group 항목이 비연속으로 섞여 있으면 SuggestionMenu가 동일\r\n // group 헤더를 중복 렌더해 React key 충돌이 난다(예: ko 로케일에서\r\n // \"제목\" group이 분리됨). group을 안정 정렬해 연속되도록 만든다.\r\n const groupOrder: string[] = [];\r\n for (const it of allItems) {\r\n const g = (it as any).group ?? \"\";\r\n if (!groupOrder.includes(g)) groupOrder.push(g);\r\n }\r\n allItems.sort(\r\n (a: any, b: any) =>\r\n groupOrder.indexOf(a.group ?? \"\") -\r\n groupOrder.indexOf(b.group ?? \"\")\r\n );\r\n\r\n if (!query) return allItems;\r\n const q = query.toLowerCase();\r\n return allItems.filter(\r\n (item: any) =>\r\n item.title?.toLowerCase().includes(q) ||\r\n (item.aliases || []).some((a: string) =>\r\n a.toLowerCase().includes(q)\r\n )\r\n );\r\n },\r\n [editor, allowVideoUpload, linkPreview?.apiEndpoint]\r\n )}\r\n />\r\n }\r\n {!sideMenuAddButton && (\r\n <SideMenuController sideMenu={DragHandleOnlySideMenu} />\r\n )}\r\n </BlockNoteView>\r\n\r\n {/* 이미지/비디오 업로드 로딩 스피너 */}\r\n {isUploading && (\r\n <div className=\"lumirEditor-upload-overlay\">\r\n <div className=\"lumirEditor-spinner\" />\r\n {uploadProgress !== null && (\r\n <span className=\"lumirEditor-upload-progress\">{uploadProgress}%</span>\r\n )}\r\n </div>\r\n )}\r\n\r\n {/* 에러 메시지 토스트 */}\r\n {errorMessage && (\r\n <div className=\"lumirEditor-error-toast\">\r\n <span className=\"lumirEditor-error-icon\">⚠️</span>\r\n <span className=\"lumirEditor-error-message\">{errorMessage}</span>\r\n <button\r\n className=\"lumirEditor-error-close\"\r\n onClick={() => setErrorMessage(null)}\r\n type=\"button\"\r\n >\r\n ✕\r\n </button>\r\n </div>\r\n )}\r\n </div>\r\n );\r\n}\r\n","// clsx와 tailwind-merge를 사용한 className 유틸리티\r\n// 사용자가 직접 설치하도록 권장하거나, 간단한 버전 제공\r\n\r\nexport function cn(...inputs: (string | undefined | null | false)[]) {\r\n return inputs.filter(Boolean).join(' ');\r\n}\r\n","export interface S3UploaderConfig {\r\n apiEndpoint: string; // '/api/s3/presigned'(필수)\r\n env: \"production\" | \"development\"; // 환경 (필수)\r\n path: string; // 파일 경로 (필수)\r\n /** 파일명 변환 콜백 - 확장자를 제외한 파일명을 받아 변환합니다 */\r\n fileNameTransform?: (nameWithoutExt: string, file: File) => string;\r\n /** true일 경우 파일명 뒤에 UUID를 자동으로 추가합니다 (예: image_abc123.png) */\r\n appendUUID?: boolean;\r\n /** false로 설정하면 확장자를 자동으로 붙이지 않음 (기본: true) */\r\n preserveExtension?: boolean;\r\n /** 업로드 진행률(0–100) 콜백. S3 PUT 시에만 호출됨 */\r\n onProgress?: (percent: number) => void;\r\n /** PUT 요청 타임아웃(ms). 미설정 시 120000(120초). 대용량 비디오에 유리 */\r\n uploadTimeoutMs?: number;\r\n /** PUT 실패 시 재시도 횟수. 기본 2(최대 3회 시도) */\r\n maxRetries?: number;\r\n}\r\n\r\n/**\r\n * 🔒 보안: S3 URL 검증\r\n * HTTPS 프로토콜 강제 및 URL 형식 검증\r\n */\r\nfunction validateS3Url(url: unknown, fieldName: string): string {\r\n // 타입 검증\r\n if (typeof url !== \"string\" || !url || url.trim() === \"\") {\r\n throw new Error(\r\n `${fieldName} is required and must be a non-empty string`\r\n );\r\n }\r\n\r\n // HTTPS 프로토콜 강제 (SSRF 방지)\r\n if (!url.startsWith(\"https://\")) {\r\n throw new Error(`${fieldName} must use HTTPS protocol`);\r\n }\r\n\r\n // URL 형식 검증\r\n try {\r\n const urlObj = new URL(url);\r\n // 추가 검증: localhost, private IP 차단\r\n const hostname = urlObj.hostname.toLowerCase();\r\n if (\r\n hostname === \"localhost\" ||\r\n hostname.startsWith(\"127.\") ||\r\n hostname.startsWith(\"192.168.\") ||\r\n hostname.startsWith(\"10.\") ||\r\n hostname === \"169.254.169.254\" // AWS 메타데이터 서버\r\n ) {\r\n throw new Error(`${fieldName} cannot point to internal/private networks`);\r\n }\r\n } catch (error) {\r\n if (error instanceof Error && error.message.includes(\"cannot point to\")) {\r\n throw error;\r\n }\r\n throw new Error(`${fieldName} is not a valid URL format`);\r\n }\r\n\r\n return url;\r\n}\r\n\r\n// UUID 생성 함수 (crypto.randomUUID 또는 폴백)\r\nconst generateUUID = (): string => {\r\n if (typeof crypto !== \"undefined\" && crypto.randomUUID) {\r\n return crypto.randomUUID();\r\n }\r\n // 폴백: 간단한 UUID v4 형식 생성\r\n return \"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx\".replace(/[xy]/g, (c) => {\r\n const r = (Math.random() * 16) | 0;\r\n const v = c === \"x\" ? r : (r & 0x3) | 0x8;\r\n return v.toString(16);\r\n });\r\n};\r\n\r\nexport const createS3Uploader = (config: S3UploaderConfig) => {\r\n const {\r\n apiEndpoint,\r\n env,\r\n path,\r\n fileNameTransform,\r\n appendUUID,\r\n preserveExtension = true,\r\n onProgress,\r\n uploadTimeoutMs = 120000,\r\n maxRetries = 2,\r\n } = config;\r\n\r\n // 필수 파라미터 검증\r\n if (!apiEndpoint || apiEndpoint.trim() === \"\") {\r\n throw new Error(\r\n \"apiEndpoint is required for S3 upload. Please provide a valid API endpoint.\"\r\n );\r\n }\r\n\r\n if (!env) {\r\n throw new Error(\"env is required. Must be 'development' or 'production'.\");\r\n }\r\n\r\n if (!path || path.trim() === \"\") {\r\n throw new Error(\"path is required and cannot be empty.\");\r\n }\r\n\r\n // 파일명에 UUID 추가하는 함수\r\n const appendUUIDToFileName = (filename: string): string => {\r\n const lastDotIndex = filename.lastIndexOf(\".\");\r\n if (lastDotIndex === -1) {\r\n // 확장자가 없는 경우\r\n return `${filename}_${generateUUID()}`;\r\n }\r\n const name = filename.substring(0, lastDotIndex);\r\n const ext = filename.substring(lastDotIndex);\r\n return `${name}_${generateUUID()}${ext}`;\r\n };\r\n\r\n // 계층 구조 파일명 생성 함수\r\n const generateHierarchicalFileName = (file: File): string => {\r\n // 0. 확장자 분리\r\n const originalName = file.name;\r\n const lastDotIndex = originalName.lastIndexOf(\".\");\r\n const nameWithoutExt =\r\n lastDotIndex === -1\r\n ? originalName\r\n : originalName.substring(0, lastDotIndex);\r\n const extension =\r\n lastDotIndex === -1 ? \"\" : originalName.substring(lastDotIndex);\r\n\r\n let filename = nameWithoutExt;\r\n\r\n // 1. 사용자 정의 파일명 변환 콜백 적용 (확장자 제외한 이름만)\r\n if (fileNameTransform) {\r\n filename = fileNameTransform(filename, file);\r\n }\r\n\r\n // 2. UUID 자동 추가 (appendUUID가 true인 경우)\r\n if (appendUUID) {\r\n filename = `${filename}_${generateUUID()}`;\r\n }\r\n\r\n // 3. 확장자 다시 붙이기 (preserveExtension이 true인 경우만)\r\n if (preserveExtension) {\r\n filename = `${filename}${extension}`;\r\n }\r\n\r\n // {env}/{path}/{filename}\r\n return `${env}/${path}/${filename}`;\r\n };\r\n\r\n const debugLog = (loc: string, msg: string, data: Record<string, unknown>) => {\r\n const p = fetch(\"http://127.0.0.1:7686/ingest/1f8ee1c5-0cf0-4ae7-91ed-5ea7ed17130a\", {\r\n method: \"POST\",\r\n headers: { \"Content-Type\": \"application/json\", \"X-Debug-Session-Id\": \"b73262\" },\r\n body: JSON.stringify({ sessionId: \"b73262\", location: loc, message: msg, data, timestamp: Date.now() }),\r\n });\r\n if (p && typeof (p as Promise<unknown>).catch === \"function\") (p as Promise<unknown>).catch(() => {});\r\n };\r\n\r\n return async (file: File): Promise<string> => {\r\n try {\r\n if (!apiEndpoint || apiEndpoint.trim() === \"\") {\r\n throw new Error(\r\n \"Invalid apiEndpoint: Cannot upload file without a valid API ENDPOINT\"\r\n );\r\n }\r\n\r\n const fileName = generateHierarchicalFileName(file);\r\n const contentType = file.type || \"application/octet-stream\";\r\n const presignedUrlFull = `${apiEndpoint}?key=${encodeURIComponent(fileName)}&contentType=${encodeURIComponent(contentType)}`;\r\n const tPresigned = Date.now();\r\n // #region agent log\r\n debugLog(\"s3:step1:presignedReq\", \"fetching presigned URL\", {\r\n fileName,\r\n contentType,\r\n apiEndpoint,\r\n });\r\n // #endregion\r\n const response = await fetch(presignedUrlFull);\r\n\r\n // #region agent log\r\n debugLog(\"s3:step2:presignedRes\", \"presigned response\", {\r\n ok: response.ok,\r\n status: response.status,\r\n elapsedMs: Date.now() - tPresigned,\r\n });\r\n // #endregion\r\n if (!response.ok) {\r\n const errorText = (await response.text()) || \"\";\r\n debugLog(\"s3:step2b:presignedErr\", \"presigned failed\", {\r\n status: response.status,\r\n errorText: errorText.slice(0, 200),\r\n });\r\n throw new Error(\r\n `Failed to get presigned URL: ${response.statusText}, ${errorText}`\r\n );\r\n }\r\n\r\n const responseData = await response.json();\r\n const { presignedUrl, publicUrl } = responseData;\r\n const validatedPresignedUrl = validateS3Url(presignedUrl, \"presignedUrl\");\r\n const validatedPublicUrl = validateS3Url(publicUrl, \"publicUrl\");\r\n\r\n const tPut = Date.now();\r\n // #region agent log\r\n debugLog(\"s3:step3:putReq\", \"S3 PUT request\", { publicUrlLen: validatedPublicUrl?.length });\r\n // #endregion\r\n\r\n let lastError: Error | undefined;\r\n const attempts = maxRetries + 1;\r\n for (let attempt = 0; attempt < attempts; attempt++) {\r\n try {\r\n if (onProgress && typeof XMLHttpRequest !== \"undefined\") {\r\n await new Promise<void>((resolve, reject) => {\r\n const xhr = new XMLHttpRequest();\r\n xhr.timeout = uploadTimeoutMs;\r\n\r\n // --- 진행률 보간용 상태 (progress 이벤트가 드물 때 중간 % 표시) ---\r\n let lastReported = -1; // 마지막으로 콜백에 넘긴 퍼센트 (중복 호출 방지)\r\n const REPORT_INTERVAL_MS = 100; // 보간 타이머 주기(ms)\r\n const simulatedCap = 90; // 보간 진행률 상한(%). 90 이상은 실제 progress 값 사용\r\n let simulatedPercent = 0;\r\n\r\n // 보간 타이머: progress 이벤트가 잘 오지 않을 때 주기적으로 진행률 상승\r\n const simId = setInterval(() => {\r\n if (simulatedPercent >= simulatedCap) return;\r\n simulatedPercent = Math.min(simulatedCap, simulatedPercent + 3);\r\n const p = Math.min(100, simulatedPercent);\r\n if (p > lastReported) {\r\n lastReported = p;\r\n onProgress(p);\r\n }\r\n }, REPORT_INTERVAL_MS);\r\n const clearSim = () => {\r\n clearInterval(simId);\r\n };\r\n\r\n // XHR 실제 업로드 진행률 (loaded/total)\r\n xhr.upload.onprogress = (e) => {\r\n if (e.lengthComputable) {\r\n const p = Math.min(100, Math.round((e.loaded / e.total) * 100));\r\n if (p >= simulatedCap) clearSim(); // 실제 진행이 90% 넘으면 보간 타이머 중단\r\n const toReport = Math.max(p, simulatedPercent); // 실제 vs 보간 중 큰 값 사용\r\n if (toReport > lastReported) {\r\n lastReported = toReport;\r\n onProgress(toReport);\r\n }\r\n }\r\n };\r\n xhr.onload = () => {\r\n clearSim();\r\n if (xhr.status >= 200 && xhr.status < 300) {\r\n if (lastReported < 100) onProgress(100); // 완료 시 100% 한 번 더 보장\r\n resolve();\r\n } else {\r\n reject(new Error(`Failed to upload file: ${xhr.statusText}`));\r\n }\r\n };\r\n xhr.onerror = () => {\r\n clearSim();\r\n reject(new Error(\"Upload failed\"));\r\n };\r\n xhr.ontimeout = () => {\r\n clearSim();\r\n reject(new Error(\"Upload timeout\"));\r\n };\r\n xhr.open(\"PUT\", validatedPresignedUrl);\r\n xhr.setRequestHeader(\"Content-Type\", file.type || \"application/octet-stream\");\r\n onProgress(0); // 업로드 시작 직후 0% 알림\r\n lastReported = 0;\r\n xhr.send(file);\r\n });\r\n } else {\r\n const controller = new AbortController();\r\n const timeoutId = setTimeout(() => controller.abort(), uploadTimeoutMs);\r\n const uploadResponse = await fetch(validatedPresignedUrl, {\r\n method: \"PUT\",\r\n headers: {\r\n \"Content-Type\": file.type || \"application/octet-stream\",\r\n },\r\n body: file,\r\n signal: controller.signal,\r\n });\r\n clearTimeout(timeoutId);\r\n\r\n // #region agent log\r\n debugLog(\"s3:step4:putRes\", \"S3 PUT response\", {\r\n ok: uploadResponse.ok,\r\n status: uploadResponse.status,\r\n putElapsedMs: Date.now() - tPut,\r\n });\r\n // #endregion\r\n if (!uploadResponse.ok) {\r\n throw new Error(`Failed to upload file: ${uploadResponse.statusText}`);\r\n }\r\n }\r\n debugLog(\"s3:step5:return\", \"returning publicUrl\", { urlPrefix: validatedPublicUrl.slice(0, 80) });\r\n return validatedPublicUrl;\r\n } catch (err) {\r\n lastError = err instanceof Error ? err : new Error(String(err));\r\n if (attempt < attempts - 1) {\r\n debugLog(\"s3:putRetry\", \"PUT failed, retrying\", { attempt: attempt + 1, attempts });\r\n } else {\r\n throw lastError;\r\n }\r\n }\r\n }\r\n\r\n throw lastError ?? new Error(\"Upload failed\");\r\n } catch (error) {\r\n console.error(\"S3 upload failed:\", error);\r\n throw error;\r\n }\r\n };\r\n};\r\n","import { createReactBlockSpec } from \"@blocknote/react\";\r\nimport {\r\n defaultBlockSpecs,\r\n BlockNoteSchema,\r\n defaultInlineContentSpecs,\r\n defaultStyleSpecs,\r\n createBlockSpecFromStronglyTypedTiptapNode,\r\n} from \"@blocknote/core\";\r\nimport { LinkPreviewBlock } from \"./LinkPreview\";\r\nimport { VideoBlock } from \"./VideoBlock\";\r\nimport { ColumnList } from \"./columns/ColumnList\";\r\nimport { Column } from \"./columns/Column\";\r\nimport { FontSize } from \"../styles/FontSizeStyle\";\r\nimport { useState, useRef, useCallback, useEffect } from \"react\";\r\n\r\n// HTML 미리보기 블록 속성 타입\r\nexport interface HtmlPreviewProps {\r\n htmlContent: string;\r\n fileName?: string;\r\n height?: string;\r\n}\r\n\r\n// 최소/최대 높이 상수\r\n/** @internal 테스트용 export */\r\nexport const MIN_HEIGHT = 100;\r\n/** @internal 테스트용 export */\r\nexport const MAX_HEIGHT = 1200;\r\n\r\n// ============================================\r\n// 보안 유틸리티 함수\r\n// ============================================\r\n\r\n/**\r\n * HTML에 charset이 없으면 UTF-8 meta 태그 추가\r\n * (원본 HTML을 최소한으로만 수정하여 인코딩 깨짐 방지)\r\n * @internal 테스트용 export\r\n */\r\nexport const ensureCharset = (html: string): string => {\r\n // 이미 charset이 있으면 원본 그대로 반환\r\n const hasCharset = /<meta[^>]+charset\\s*=/i.test(html);\r\n if (hasCharset) {\r\n return html;\r\n }\r\n\r\n // <head> 태그가 있으면 그 안에 추가\r\n if (/<head[^>]*>/i.test(html)) {\r\n return html.replace(/(<head[^>]*>)/i, '$1\\n<meta charset=\"UTF-8\">');\r\n }\r\n\r\n // <html> 태그만 있으면 <head> 추가\r\n if (/<html[^>]*>/i.test(html)) {\r\n return html.replace(\r\n /(<html[^>]*>)/i,\r\n '$1\\n<head><meta charset=\"UTF-8\"></head>'\r\n );\r\n }\r\n\r\n // HTML fragment인 경우 최소한의 구조 추가\r\n return `<!DOCTYPE html>\r\n<html>\r\n<head><meta charset=\"UTF-8\"></head>\r\n<body>\r\n${html}\r\n</body>\r\n</html>`;\r\n};\r\n\r\n/**\r\n * 파일명 새니타이제이션 (경로 조작 방지)\r\n * @internal 테스트용 export\r\n */\r\nexport const sanitizeFileName = (fileName: string): string => {\r\n if (!fileName || typeof fileName !== \"string\") {\r\n return `document_${Date.now()}.html`;\r\n }\r\n\r\n return (\r\n fileName\r\n .replace(/\\0/g, \"\") // Null byte 제거\r\n .replace(/[\\/\\\\]/g, \"_\") // 경로 구분자 제거\r\n .replace(/[<>:\"|?*\\x00-\\x1f]/g, \"\") // 위험한 문자 제거\r\n .replace(/\\.{2,}/g, \".\") // 연속된 점 제거\r\n .trim()\r\n .replace(/^\\.+|\\.+$/g, \"\") || `document_${Date.now()}.html` // 앞뒤 점 제거\r\n );\r\n};\r\n\r\n/**\r\n * Blob URL 생성 (UTF-8 인코딩 명시)\r\n * @internal 테스트용 export\r\n */\r\nexport const createSecureBlobUrl = (htmlContent: string): string => {\r\n const htmlWithCharset = ensureCharset(htmlContent);\r\n\r\n // UTF-8 인코딩 명시\r\n const blob = new Blob([htmlWithCharset], {\r\n type: \"text/html;charset=utf-8\",\r\n });\r\n\r\n return URL.createObjectURL(blob);\r\n};\r\n\r\n// ============================================\r\n// HTML 미리보기 블록 스펙\r\n// ============================================\r\n\r\nexport const HtmlPreviewBlock = createReactBlockSpec(\r\n {\r\n type: \"htmlPreview\",\r\n propSchema: {\r\n htmlContent: {\r\n default: \"\",\r\n },\r\n fileName: {\r\n default: \"\",\r\n },\r\n height: {\r\n default: \"400px\",\r\n },\r\n },\r\n content: \"none\",\r\n },\r\n {\r\n render: (props) => {\r\n const [isExpanded, setIsExpanded] = useState(true);\r\n const [isResizing, setIsResizing] = useState(false);\r\n const [blobUrl, setBlobUrl] = useState<string>(\"\");\r\n const containerRef = useRef<HTMLDivElement>(null);\r\n\r\n const htmlContent = props.block.props.htmlContent || \"\";\r\n const fileName = props.block.props.fileName || \"HTML Document\";\r\n const savedHeight = props.block.props.height || \"400px\";\r\n\r\n // 현재 높이 (숫자로 파싱)\r\n const currentHeight = parseInt(savedHeight, 10) || 400;\r\n\r\n // UTF-8 인코딩 보장된 Blob URL 생성\r\n useEffect(() => {\r\n if (htmlContent) {\r\n const url = createSecureBlobUrl(htmlContent);\r\n setBlobUrl(url);\r\n\r\n return () => {\r\n URL.revokeObjectURL(url);\r\n };\r\n }\r\n }, [htmlContent]);\r\n\r\n // 리사이즈 시작\r\n const handleResizeStart = useCallback(\r\n (e: React.MouseEvent) => {\r\n e.preventDefault();\r\n e.stopPropagation();\r\n setIsResizing(true);\r\n\r\n const startY = e.clientY;\r\n const startHeight = currentHeight;\r\n\r\n const handleMouseMove = (moveEvent: MouseEvent) => {\r\n const deltaY = moveEvent.clientY - startY;\r\n const newHeight = Math.min(\r\n MAX_HEIGHT,\r\n Math.max(MIN_HEIGHT, startHeight + deltaY)\r\n );\r\n\r\n // 블록 props 업데이트 (저장됨)\r\n props.editor.updateBlock(props.block, {\r\n props: { height: `${newHeight}px` },\r\n });\r\n };\r\n\r\n const handleMouseUp = () => {\r\n setIsResizing(false);\r\n document.removeEventListener(\"mousemove\", handleMouseMove);\r\n document.removeEventListener(\"mouseup\", handleMouseUp);\r\n };\r\n\r\n document.addEventListener(\"mousemove\", handleMouseMove);\r\n document.addEventListener(\"mouseup\", handleMouseUp);\r\n },\r\n [currentHeight, props.editor, props.block]\r\n );\r\n\r\n // HTML 파일 다운로드 (원본 그대로 + 인코딩 보장)\r\n const handleExport = useCallback(\r\n (e: React.MouseEvent) => {\r\n e.stopPropagation();\r\n\r\n // 파일명 새니타이제이션 (경로 조작 방지)\r\n const safeFileName = sanitizeFileName(fileName);\r\n const downloadName = safeFileName.endsWith(\".html\")\r\n ? safeFileName\r\n : `${safeFileName}.html`;\r\n\r\n // UTF-8 인코딩 명시\r\n const htmlWithCharset = ensureCharset(htmlContent);\r\n const blob = new Blob([htmlWithCharset], {\r\n type: \"text/html;charset=utf-8\",\r\n });\r\n\r\n const url = URL.createObjectURL(blob);\r\n const a = document.createElement(\"a\");\r\n a.href = url;\r\n a.download = downloadName;\r\n a.rel = \"noopener noreferrer\"; // 보안 속성 추가\r\n\r\n document.body.appendChild(a);\r\n a.click();\r\n document.body.removeChild(a);\r\n URL.revokeObjectURL(url);\r\n },\r\n [htmlContent, fileName]\r\n );\r\n\r\n // 새 창에서 열기 (Blob URL 방식 - XSS 방지)\r\n const handleOpenNewWindow = useCallback(\r\n (e: React.MouseEvent) => {\r\n e.stopPropagation();\r\n\r\n // 클라이언트 사이드에서만 실행\r\n if (typeof window === \"undefined\") return;\r\n\r\n // Blob URL 생성 (UTF-8 인코딩 보장)\r\n const url = createSecureBlobUrl(htmlContent);\r\n\r\n // noopener, noreferrer로 보안 강화\r\n const newWindow = window.open(url, \"_blank\", \"noopener,noreferrer\");\r\n\r\n // Blob URL 정리\r\n if (newWindow) {\r\n setTimeout(() => URL.revokeObjectURL(url), 1000);\r\n } else {\r\n URL.revokeObjectURL(url);\r\n }\r\n },\r\n [htmlContent]\r\n );\r\n\r\n return (\r\n <div\r\n ref={containerRef}\r\n style={{\r\n border: \"1px solid #e0e0e0\",\r\n borderRadius: \"8px\",\r\n overflow: \"hidden\",\r\n backgroundColor: \"#f9f9f9\",\r\n marginBottom: \"2px\",\r\n width: \"100%\",\r\n userSelect: isResizing ? \"none\" : \"auto\",\r\n outline: \"none\",\r\n boxShadow: \"none\",\r\n }}\r\n >\r\n {/* 헤더 */}\r\n <div\r\n style={{\r\n display: \"flex\",\r\n alignItems: \"center\",\r\n justifyContent: \"space-between\",\r\n padding: \"4px 16px\",\r\n backgroundColor: \"#fff\",\r\n borderBottom: isExpanded ? \"1px solid #e0e0e0\" : \"none\",\r\n }}\r\n >\r\n <div\r\n style={{\r\n display: \"flex\",\r\n alignItems: \"center\",\r\n gap: \"8px\",\r\n cursor: \"pointer\",\r\n flex: 1,\r\n }}\r\n onClick={() => setIsExpanded(!isExpanded)}\r\n >\r\n <svg\r\n width=\"16\"\r\n height=\"16\"\r\n viewBox=\"0 0 24 24\"\r\n fill=\"none\"\r\n stroke=\"currentColor\"\r\n strokeWidth=\"2\"\r\n strokeLinecap=\"round\"\r\n strokeLinejoin=\"round\"\r\n style={{\r\n transform: isExpanded ? \"rotate(180deg)\" : \"rotate(0deg)\",\r\n transition: \"transform 0.2s\",\r\n }}\r\n >\r\n <polyline points=\"6 9 12 15 18 9\"></polyline>\r\n </svg>\r\n\r\n <span style={{ fontWeight: 500, fontSize: \"14px\" }}>\r\n {fileName}\r\n </span>\r\n </div>\r\n\r\n {/* 액션 버튼들 */}\r\n <div style={{ display: \"flex\", alignItems: \"center\", gap: \"4px\" }}>\r\n {/* 새 창에서 열기 버튼 */}\r\n <button\r\n onClick={handleOpenNewWindow}\r\n style={{\r\n background: \"none\",\r\n border: \"none\",\r\n cursor: \"pointer\",\r\n padding: \"4px\",\r\n display: \"flex\",\r\n alignItems: \"center\",\r\n justifyContent: \"center\",\r\n color: \"#666\",\r\n borderRadius: \"4px\",\r\n }}\r\n title=\"새 창에서 열기\"\r\n type=\"button\"\r\n onMouseEnter={(e) => {\r\n (e.currentTarget as HTMLButtonElement).style.backgroundColor =\r\n \"#f0f0f0\";\r\n }}\r\n onMouseLeave={(e) => {\r\n (e.currentTarget as HTMLButtonElement).style.backgroundColor =\r\n \"transparent\";\r\n }}\r\n >\r\n <svg\r\n width=\"16\"\r\n height=\"16\"\r\n viewBox=\"0 0 24 24\"\r\n fill=\"none\"\r\n stroke=\"currentColor\"\r\n strokeWidth=\"2\"\r\n strokeLinecap=\"round\"\r\n strokeLinejoin=\"round\"\r\n >\r\n <path d=\"M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6\"></path>\r\n <polyline points=\"15 3 21 3 21 9\"></polyline>\r\n <line x1=\"10\" y1=\"14\" x2=\"21\" y2=\"3\"></line>\r\n </svg>\r\n </button>\r\n\r\n {/* 다운로드 버튼 */}\r\n <button\r\n onClick={handleExport}\r\n style={{\r\n background: \"none\",\r\n border: \"none\",\r\n cursor: \"pointer\",\r\n padding: \"4px\",\r\n display: \"flex\",\r\n alignItems: \"center\",\r\n justifyContent: \"center\",\r\n color: \"#666\",\r\n borderRadius: \"4px\",\r\n }}\r\n title=\"HTML 다운로드\"\r\n type=\"button\"\r\n onMouseEnter={(e) => {\r\n (e.currentTarget as HTMLButtonElement).style.backgroundColor =\r\n \"#f0f0f0\";\r\n }}\r\n onMouseLeave={(e) => {\r\n (e.currentTarget as HTMLButtonElement).style.backgroundColor =\r\n \"transparent\";\r\n }}\r\n >\r\n <svg\r\n width=\"16\"\r\n height=\"16\"\r\n viewBox=\"0 0 24 24\"\r\n fill=\"none\"\r\n stroke=\"currentColor\"\r\n strokeWidth=\"2\"\r\n strokeLinecap=\"round\"\r\n strokeLinejoin=\"round\"\r\n >\r\n <path d=\"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4\"></path>\r\n <polyline points=\"7 10 12 15 17 10\"></polyline>\r\n <line x1=\"12\" y1=\"15\" x2=\"12\" y2=\"3\"></line>\r\n </svg>\r\n </button>\r\n </div>\r\n </div>\r\n\r\n {/* iframe 미리보기 */}\r\n {isExpanded && (\r\n <div\r\n style={{\r\n padding: \"0\",\r\n backgroundColor: \"#fff\",\r\n position: \"relative\",\r\n }}\r\n >\r\n {/* 🔒 보안 강화: JavaScript 완전 차단 + 부모 페이지 접근 차단 */}\r\n <iframe\r\n src={blobUrl || \"about:blank\"}\r\n style={{\r\n width: \"100%\",\r\n height: `${currentHeight}px`,\r\n border: \"none\",\r\n display: \"block\",\r\n pointerEvents: isResizing ? \"none\" : \"auto\",\r\n }}\r\n // 🔒 allow-scripts 제거 = JavaScript 실행 차단\r\n // 🔒 allow-same-origin 제거 = 부모 페이지 접근 차단\r\n // ✅ HTML + CSS만 렌더링 (안전)\r\n sandbox=\"allow-popups allow-forms\"\r\n title={fileName}\r\n referrerPolicy=\"no-referrer\"\r\n loading=\"lazy\"\r\n />\r\n\r\n {/* 리사이즈 핸들 */}\r\n <div\r\n onMouseDown={handleResizeStart}\r\n style={{\r\n position: \"absolute\",\r\n bottom: 0,\r\n left: 0,\r\n right: 0,\r\n height: \"12px\",\r\n cursor: \"ns-resize\",\r\n backgroundColor: isResizing\r\n ? \"rgba(59, 130, 246, 0.3)\"\r\n : \"transparent\",\r\n display: \"flex\",\r\n alignItems: \"center\",\r\n justifyContent: \"center\",\r\n transition: \"background-color 0.2s\",\r\n }}\r\n onMouseEnter={(e) => {\r\n (e.currentTarget as HTMLDivElement).style.backgroundColor =\r\n \"rgba(59, 130, 246, 0.2)\";\r\n }}\r\n onMouseLeave={(e) => {\r\n if (!isResizing) {\r\n (e.currentTarget as HTMLDivElement).style.backgroundColor =\r\n \"transparent\";\r\n }\r\n }}\r\n >\r\n {/* 리사이즈 핸들 아이콘 */}\r\n <div\r\n style={{\r\n width: \"40px\",\r\n height: \"4px\",\r\n backgroundColor: \"#ccc\",\r\n borderRadius: \"2px\",\r\n }}\r\n />\r\n </div>\r\n </div>\r\n )}\r\n </div>\r\n );\r\n },\r\n }\r\n);\r\n\r\n// 다단(컬럼) 블록 스펙 — column/columnList Tiptap 노드를 BlockNote 블록 스펙으로 등록.\r\n// content는 자동으로 \"none\"으로 매핑되고 children은 childContainer에서 읽힌다 → nodeToBlock\r\n// (직렬화)이 columnList/column을 인식해 라운드트립된다.\r\nconst ColumnListBlock = createBlockSpecFromStronglyTypedTiptapNode(\r\n ColumnList,\r\n // showDivider를 블록 prop으로 등록 → onContentChange JSON 직렬화 + 재로드 라운드트립.\r\n { showDivider: { default: false } },\r\n);\r\nconst ColumnBlock = createBlockSpecFromStronglyTypedTiptapNode(Column, {});\r\n\r\n// 커스텀 블록이 포함된 스키마 생성\r\nexport const schema = BlockNoteSchema.create({\r\n blockSpecs: {\r\n ...defaultBlockSpecs,\r\n htmlPreview: HtmlPreviewBlock,\r\n linkPreview: LinkPreviewBlock,\r\n video: VideoBlock,\r\n columnList: ColumnListBlock,\r\n column: ColumnBlock,\r\n },\r\n inlineContentSpecs: defaultInlineContentSpecs,\r\n styleSpecs: {\r\n ...defaultStyleSpecs,\r\n // 인라인 글자 크기. 저장 JSON 직렬화는 형제 키 방식(font-size-serialization.ts) 사용.\r\n fontSize: FontSize,\r\n },\r\n});\r\n\r\n// 스키마 타입 export\r\nexport type HtmlPreviewSchema = typeof schema;\r\n","import { createReactBlockSpec } from \"@blocknote/react\";\r\nimport React, { useState, useEffect, useCallback, useRef } from \"react\";\r\nimport { DEFAULT_LINK_PREVIEW_IMAGE } from \"./defaultLogo\";\r\n\r\nexport interface LinkMetadata {\r\n url: string;\r\n title: string;\r\n description?: string;\r\n image?: string;\r\n domain: string;\r\n}\r\n\r\nconst metadataCache = new Map<string, LinkMetadata>();\r\n\r\nexport async function fetchLinkMetadata(\r\n url: string,\r\n apiEndpoint: string\r\n): Promise<LinkMetadata> {\r\n const cached = metadataCache.get(url);\r\n if (cached) return cached;\r\n\r\n const response = await fetch(\r\n `${apiEndpoint}?url=${encodeURIComponent(url)}`\r\n );\r\n if (!response.ok) {\r\n throw new Error(`Failed to fetch metadata: ${response.status}`);\r\n }\r\n\r\n const metadata: LinkMetadata = await response.json();\r\n metadataCache.set(url, metadata);\r\n return metadata;\r\n}\r\n\r\nexport function clearMetadataCache() {\r\n metadataCache.clear();\r\n}\r\n\r\nfunction extractDomain(url: string): string {\r\n try {\r\n return new URL(url).hostname.replace(/^www\\./, \"\");\r\n } catch {\r\n return url;\r\n }\r\n}\r\n\r\nconst LinkPreviewCard = ({\r\n url,\r\n title,\r\n description,\r\n image,\r\n domain,\r\n editable,\r\n onDelete,\r\n width,\r\n onWidthChange,\r\n height,\r\n onHeightChange,\r\n}: {\r\n url: string;\r\n title: string;\r\n description: string;\r\n image: string;\r\n domain: string;\r\n editable: boolean;\r\n onDelete?: () => void;\r\n width?: number;\r\n onWidthChange?: (width: number) => void;\r\n height?: number;\r\n onHeightChange?: (height: number) => void;\r\n}) => {\r\n const [imgError, setImgError] = useState(false);\r\n const [hovered, setHovered] = useState(false);\r\n\r\n type ResizeParams = {\r\n handleUsed: \"left\" | \"right\" | \"bottom\";\r\n initialClientX: number;\r\n initialClientY: number;\r\n initialWidth: number;\r\n initialHeight: number;\r\n };\r\n const [resizeParams, setResizeParams] = useState<ResizeParams | undefined>(undefined);\r\n const [localWidth, setLocalWidth] = useState<number | undefined>(width);\r\n const [localHeight, setLocalHeight] = useState<number | undefined>(height);\r\n const cardRef = useRef<HTMLDivElement>(null);\r\n const justResizedRef = useRef(false);\r\n\r\n useEffect(() => { setLocalWidth(width); }, [width]);\r\n useEffect(() => { setLocalHeight(height); }, [height]);\r\n\r\n useEffect(() => {\r\n if (!resizeParams) return;\r\n\r\n const onMouseMove = (e: MouseEvent) => {\r\n if (resizeParams.handleUsed === \"bottom\") {\r\n const delta = e.clientY - resizeParams.initialClientY;\r\n setLocalHeight(Math.min(Math.max(resizeParams.initialHeight + delta, 100), 600));\r\n } else {\r\n const delta = resizeParams.handleUsed === \"left\"\r\n ? resizeParams.initialClientX - e.clientX\r\n : e.clientX - resizeParams.initialClientX;\r\n setLocalWidth(Math.min(\r\n Math.max(resizeParams.initialWidth + delta, 200),\r\n cardRef.current?.parentElement?.clientWidth || 800\r\n ));\r\n }\r\n };\r\n\r\n const onMouseUp = () => {\r\n const handle = resizeParams.handleUsed;\r\n setResizeParams(undefined);\r\n justResizedRef.current = true;\r\n setTimeout(() => { justResizedRef.current = false; }, 50);\r\n if (handle === \"bottom\") {\r\n if (localHeight && onHeightChange) onHeightChange(localHeight);\r\n } else {\r\n if (localWidth && onWidthChange) onWidthChange(localWidth);\r\n }\r\n };\r\n\r\n window.addEventListener(\"mousemove\", onMouseMove);\r\n window.addEventListener(\"mouseup\", onMouseUp);\r\n return () => {\r\n window.removeEventListener(\"mousemove\", onMouseMove);\r\n window.removeEventListener(\"mouseup\", onMouseUp);\r\n };\r\n }, [resizeParams, localWidth, localHeight, onWidthChange, onHeightChange]);\r\n\r\n const handleLeftDown = useCallback((e: React.MouseEvent) => {\r\n e.preventDefault();\r\n e.stopPropagation();\r\n setResizeParams({\r\n handleUsed: \"left\",\r\n initialWidth: cardRef.current!.clientWidth,\r\n initialHeight: localHeight || 200,\r\n initialClientX: e.clientX,\r\n initialClientY: e.clientY,\r\n });\r\n }, [localHeight]);\r\n\r\n const handleRightDown = useCallback((e: React.MouseEvent) => {\r\n e.preventDefault();\r\n e.stopPropagation();\r\n setResizeParams({\r\n handleUsed: \"right\",\r\n initialWidth: cardRef.current!.clientWidth,\r\n initialHeight: localHeight || 200,\r\n initialClientX: e.clientX,\r\n initialClientY: e.clientY,\r\n });\r\n }, [localHeight]);\r\n\r\n const handleBottomDown = useCallback((e: React.MouseEvent) => {\r\n e.preventDefault();\r\n e.stopPropagation();\r\n setResizeParams({\r\n handleUsed: \"bottom\",\r\n initialWidth: cardRef.current!.clientWidth,\r\n initialHeight: localHeight || 200,\r\n initialClientX: e.clientX,\r\n initialClientY: e.clientY,\r\n });\r\n }, [localHeight]);\r\n\r\n const handleClick = useCallback(() => {\r\n if (url && !resizeParams && !justResizedRef.current) {\r\n window.open(url, \"_blank\", \"noopener,noreferrer\");\r\n }\r\n }, [url, resizeParams]);\r\n\r\n const resizeCursor = resizeParams\r\n ? resizeParams.handleUsed === \"bottom\" ? \"ns-resize\" : \"ew-resize\"\r\n : \"pointer\";\r\n\r\n return (\r\n <div\r\n ref={cardRef}\r\n className=\"lumir-link-preview-card\"\r\n onClick={handleClick}\r\n style={{\r\n border: \"1px solid #e0e0e0\",\r\n borderRadius: \"8px\",\r\n overflow: \"hidden\",\r\n cursor: resizeCursor,\r\n width: localWidth ? `${localWidth}px` : undefined,\r\n maxWidth: \"100%\",\r\n backgroundColor: \"#fff\",\r\n transition: resizeParams ? \"none\" : \"box-shadow 0.2s\",\r\n position: \"relative\",\r\n }}\r\n onMouseEnter={() => {\r\n if (!resizeParams) setHovered(true);\r\n }}\r\n onMouseLeave={() => {\r\n if (!resizeParams) setHovered(false);\r\n }}\r\n >\r\n {editable && onDelete && (\r\n <button\r\n type=\"button\"\r\n onClick={(e) => {\r\n e.stopPropagation();\r\n onDelete();\r\n }}\r\n className=\"lumir-link-preview-delete\"\r\n style={{\r\n position: \"absolute\",\r\n top: \"8px\",\r\n right: \"8px\",\r\n width: \"24px\",\r\n height: \"24px\",\r\n borderRadius: \"50%\",\r\n border: \"none\",\r\n backgroundColor: \"rgba(0,0,0,0.5)\",\r\n color: \"#fff\",\r\n cursor: \"pointer\",\r\n display: \"flex\",\r\n alignItems: \"center\",\r\n justifyContent: \"center\",\r\n fontSize: \"14px\",\r\n lineHeight: 1,\r\n zIndex: 2,\r\n opacity: 0,\r\n transition: \"opacity 0.2s\",\r\n }}\r\n title=\"삭제\"\r\n >\r\n ✕\r\n </button>\r\n )}\r\n\r\n {editable && (hovered || resizeParams) && (\r\n <>\r\n <div\r\n className=\"lumir-resize-handle\"\r\n style={{ left: \"4px\" }}\r\n onMouseDown={handleLeftDown}\r\n />\r\n <div\r\n className=\"lumir-resize-handle\"\r\n style={{ right: \"4px\" }}\r\n onMouseDown={handleRightDown}\r\n />\r\n </>\r\n )}\r\n\r\n {image && !imgError && (\r\n <div\r\n style={{\r\n width: \"100%\",\r\n height: `${localHeight || 200}px`,\r\n overflow: \"hidden\",\r\n backgroundColor: \"#f0f0f0\",\r\n position: \"relative\",\r\n }}\r\n >\r\n <img\r\n src={image}\r\n alt={title || \"Link preview\"}\r\n onError={() => setImgError(true)}\r\n style={{\r\n width: \"100%\",\r\n height: \"100%\",\r\n objectFit: \"cover\",\r\n display: \"block\",\r\n }}\r\n referrerPolicy=\"no-referrer\"\r\n loading=\"lazy\"\r\n />\r\n {editable && (hovered || resizeParams) && (\r\n <div\r\n className=\"lumir-resize-handle-bottom\"\r\n onMouseDown={handleBottomDown}\r\n />\r\n )}\r\n </div>\r\n )}\r\n\r\n <div style={{ padding: \"12px 16px\" }}>\r\n <div\r\n style={{\r\n fontSize: \"14px\",\r\n fontWeight: 600,\r\n color: \"#1a1a1a\",\r\n lineHeight: 1.4,\r\n marginBottom: description ? \"4px\" : \"8px\",\r\n overflow: \"hidden\",\r\n textOverflow: \"ellipsis\",\r\n whiteSpace: \"nowrap\",\r\n }}\r\n >\r\n {title || domain}\r\n </div>\r\n\r\n {description && (\r\n <div\r\n style={{\r\n fontSize: \"12px\",\r\n color: \"#666\",\r\n lineHeight: 1.4,\r\n marginBottom: \"8px\",\r\n overflow: \"hidden\",\r\n textOverflow: \"ellipsis\",\r\n display: \"-webkit-box\",\r\n WebkitLineClamp: 2,\r\n WebkitBoxOrient: \"vertical\",\r\n }}\r\n >\r\n {description}\r\n </div>\r\n )}\r\n\r\n <div\r\n style={{\r\n fontSize: \"12px\",\r\n color: \"#999\",\r\n overflow: \"hidden\",\r\n textOverflow: \"ellipsis\",\r\n whiteSpace: \"nowrap\",\r\n }}\r\n >\r\n {domain}\r\n </div>\r\n </div>\r\n </div>\r\n );\r\n};\r\n\r\nconst LinkPreviewSkeleton = () => (\r\n <div\r\n className=\"lumir-link-preview-skeleton\"\r\n style={{\r\n border: \"1px solid #e0e0e0\",\r\n borderRadius: \"8px\",\r\n overflow: \"hidden\",\r\n maxWidth: \"400px\",\r\n backgroundColor: \"#fff\",\r\n }}\r\n >\r\n <div\r\n style={{\r\n width: \"100%\",\r\n height: \"200px\",\r\n backgroundColor: \"#f0f0f0\",\r\n animation: \"lumir-skeleton-pulse 1.5s ease-in-out infinite\",\r\n }}\r\n />\r\n <div style={{ padding: \"12px 16px\" }}>\r\n <div\r\n style={{\r\n height: \"16px\",\r\n width: \"70%\",\r\n backgroundColor: \"#f0f0f0\",\r\n borderRadius: \"4px\",\r\n marginBottom: \"8px\",\r\n animation: \"lumir-skeleton-pulse 1.5s ease-in-out infinite\",\r\n }}\r\n />\r\n <div\r\n style={{\r\n height: \"12px\",\r\n width: \"90%\",\r\n backgroundColor: \"#f0f0f0\",\r\n borderRadius: \"4px\",\r\n marginBottom: \"8px\",\r\n animation: \"lumir-skeleton-pulse 1.5s ease-in-out infinite\",\r\n }}\r\n />\r\n <div\r\n style={{\r\n height: \"12px\",\r\n width: \"40%\",\r\n backgroundColor: \"#f0f0f0\",\r\n borderRadius: \"4px\",\r\n animation: \"lumir-skeleton-pulse 1.5s ease-in-out infinite\",\r\n }}\r\n />\r\n </div>\r\n </div>\r\n);\r\n\r\nconst LinkPreviewError = ({\r\n url,\r\n onRetry,\r\n}: {\r\n url: string;\r\n onRetry?: () => void;\r\n}) => {\r\n const domain = (() => {\r\n try { return new URL(url).hostname; } catch { return url; }\r\n })();\r\n\r\n return (\r\n <div\r\n className=\"lumir-link-preview-error\"\r\n style={{\r\n border: \"1px solid #e0e0e0\",\r\n borderRadius: \"8px\",\r\n overflow: \"hidden\",\r\n maxWidth: \"400px\",\r\n backgroundColor: \"#fff\",\r\n cursor: \"pointer\",\r\n position: \"relative\",\r\n }}\r\n onClick={() => window.open(url, \"_blank\", \"noopener,noreferrer\")}\r\n >\r\n <div\r\n style={{\r\n width: \"100%\",\r\n height: \"160px\",\r\n overflow: \"hidden\",\r\n backgroundColor: \"#f0f0f0\",\r\n display: \"flex\",\r\n alignItems: \"center\",\r\n justifyContent: \"center\",\r\n }}\r\n >\r\n <img\r\n src={DEFAULT_LINK_PREVIEW_IMAGE}\r\n alt=\"Lumir\"\r\n style={{\r\n width: \"100%\",\r\n height: \"100%\",\r\n objectFit: \"contain\",\r\n display: \"block\",\r\n padding: \"20px\",\r\n boxSizing: \"border-box\",\r\n }}\r\n />\r\n </div>\r\n <div style={{ padding: \"12px 16px\" }}>\r\n <div\r\n style={{\r\n fontSize: \"13px\",\r\n color: \"#999\",\r\n marginBottom: \"4px\",\r\n }}\r\n >\r\n 미리보기를 불러올 수 없습니다\r\n </div>\r\n <div\r\n style={{\r\n fontSize: \"12px\",\r\n color: \"#1a73e8\",\r\n overflow: \"hidden\",\r\n textOverflow: \"ellipsis\",\r\n whiteSpace: \"nowrap\",\r\n }}\r\n >\r\n {domain}\r\n </div>\r\n </div>\r\n {onRetry && (\r\n <button\r\n type=\"button\"\r\n onClick={(e) => { e.stopPropagation(); onRetry(); }}\r\n style={{\r\n position: \"absolute\",\r\n top: \"8px\",\r\n right: \"8px\",\r\n background: \"rgba(0,0,0,0.5)\",\r\n border: \"none\",\r\n borderRadius: \"4px\",\r\n padding: \"4px 8px\",\r\n cursor: \"pointer\",\r\n fontSize: \"12px\",\r\n color: \"#fff\",\r\n whiteSpace: \"nowrap\",\r\n }}\r\n >\r\n 재시도\r\n </button>\r\n )}\r\n </div>\r\n );\r\n};\r\n\r\nconst LinkPreviewUrlInput = ({\r\n onSubmit,\r\n}: {\r\n onSubmit: (url: string) => void;\r\n}) => {\r\n const [inputUrl, setInputUrl] = useState(\"\");\r\n const inputRef = useRef<HTMLInputElement>(null);\r\n\r\n useEffect(() => {\r\n inputRef.current?.focus();\r\n }, []);\r\n\r\n const handleSubmit = useCallback(() => {\r\n const trimmed = inputUrl.trim();\r\n if (!trimmed) return;\r\n const normalized = /^https?:\\/\\//i.test(trimmed)\r\n ? trimmed\r\n : `https://${trimmed}`;\r\n onSubmit(normalized);\r\n }, [inputUrl, onSubmit]);\r\n\r\n return (\r\n <div\r\n className=\"lumir-link-preview-input\"\r\n style={{\r\n border: \"1px solid #e0e0e0\",\r\n borderRadius: \"8px\",\r\n padding: \"16px\",\r\n maxWidth: \"400px\",\r\n backgroundColor: \"#fafafa\",\r\n display: \"flex\",\r\n alignItems: \"center\",\r\n gap: \"8px\",\r\n }}\r\n >\r\n <svg\r\n width=\"18\"\r\n height=\"18\"\r\n viewBox=\"0 0 24 24\"\r\n fill=\"none\"\r\n stroke=\"#999\"\r\n strokeWidth=\"2\"\r\n strokeLinecap=\"round\"\r\n strokeLinejoin=\"round\"\r\n style={{ flexShrink: 0 }}\r\n >\r\n <path d=\"M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71\" />\r\n <path d=\"M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71\" />\r\n </svg>\r\n <input\r\n ref={inputRef}\r\n type=\"text\"\r\n value={inputUrl}\r\n onChange={(e) => setInputUrl(e.target.value)}\r\n onKeyDown={(e) => {\r\n if (e.key === \"Enter\") {\r\n e.preventDefault();\r\n handleSubmit();\r\n }\r\n }}\r\n placeholder=\"URL을 입력하세요 (예: https://example.com)\"\r\n style={{\r\n flex: 1,\r\n border: \"none\",\r\n outline: \"none\",\r\n backgroundColor: \"transparent\",\r\n fontSize: \"13px\",\r\n color: \"#333\",\r\n minWidth: 0,\r\n }}\r\n />\r\n <button\r\n type=\"button\"\r\n onClick={handleSubmit}\r\n disabled={!inputUrl.trim()}\r\n style={{\r\n flexShrink: 0,\r\n padding: \"4px 12px\",\r\n border: \"none\",\r\n borderRadius: \"4px\",\r\n backgroundColor: inputUrl.trim() ? \"#3b82f6\" : \"#d1d5db\",\r\n color: \"#fff\",\r\n fontSize: \"12px\",\r\n fontWeight: 500,\r\n cursor: inputUrl.trim() ? \"pointer\" : \"not-allowed\",\r\n transition: \"background-color 0.15s\",\r\n }}\r\n >\r\n 확인\r\n </button>\r\n </div>\r\n );\r\n};\r\n\r\nexport const LinkPreviewBlock = createReactBlockSpec(\r\n {\r\n type: \"linkPreview\",\r\n propSchema: {\r\n url: { default: \"\" },\r\n title: { default: \"\" },\r\n description: { default: \"\" },\r\n image: { default: \"\" },\r\n domain: { default: \"\" },\r\n previewWidth: { default: 400 as const },\r\n previewHeight: { default: 200 as const },\r\n },\r\n content: \"none\",\r\n },\r\n {\r\n render: (props) => {\r\n const { url, title, description, image, domain } =\r\n props.block.props;\r\n const [status, setStatus] = useState<\"idle\" | \"loading\" | \"error\">(\r\n !url ? \"idle\" : title ? \"idle\" : \"loading\"\r\n );\r\n const fetchedRef = useRef(false);\r\n\r\n const editable = props.editor.isEditable;\r\n\r\n useEffect(() => {\r\n if (!url || title || fetchedRef.current) {\r\n if (!url) setStatus(\"idle\");\r\n return;\r\n }\r\n\r\n const getApiEndpoint = () =>\r\n (props.editor as any)._linkPreviewApiEndpoint as\r\n | string\r\n | undefined;\r\n\r\n const doFetch = (endpoint: string) => {\r\n fetchedRef.current = true;\r\n setStatus(\"loading\");\r\n\r\n fetchLinkMetadata(url, endpoint)\r\n .then((metadata) => {\r\n props.editor.updateBlock(props.block, {\r\n props: {\r\n title: metadata.title || \"\",\r\n description: metadata.description || \"\",\r\n image: metadata.image || \"\",\r\n domain: metadata.domain || extractDomain(url),\r\n },\r\n });\r\n setStatus(\"idle\");\r\n })\r\n .catch(() => {\r\n props.editor.updateBlock(props.block, {\r\n props: { domain: extractDomain(url) },\r\n });\r\n setStatus(\"error\");\r\n });\r\n };\r\n\r\n const endpoint = getApiEndpoint();\r\n if (endpoint) {\r\n doFetch(endpoint);\r\n } else {\r\n // endpoint가 아직 설정되지 않았을 수 있으므로 잠시 대기 후 재시도\r\n const timer = setTimeout(() => {\r\n const retryEndpoint = getApiEndpoint();\r\n if (retryEndpoint) {\r\n doFetch(retryEndpoint);\r\n } else {\r\n setStatus(\"error\");\r\n }\r\n }, 100);\r\n return () => clearTimeout(timer);\r\n }\r\n }, [url, title]);\r\n\r\n const handleRetry = useCallback(() => {\r\n const endpoint = (props.editor as any)._linkPreviewApiEndpoint as\r\n | string\r\n | undefined;\r\n if (!url || !endpoint) return;\r\n metadataCache.delete(url);\r\n fetchedRef.current = false;\r\n setStatus(\"loading\");\r\n\r\n fetchLinkMetadata(url, endpoint)\r\n .then((metadata) => {\r\n props.editor.updateBlock(props.block, {\r\n props: {\r\n title: metadata.title || \"\",\r\n description: metadata.description || \"\",\r\n image: metadata.image || \"\",\r\n domain: metadata.domain || extractDomain(url),\r\n },\r\n });\r\n setStatus(\"idle\");\r\n })\r\n .catch(() => {\r\n setStatus(\"error\");\r\n });\r\n }, [url, props.editor, props.block]);\r\n\r\n const handleDelete = useCallback(() => {\r\n props.editor.removeBlocks([props.block]);\r\n }, [props.editor, props.block]);\r\n\r\n if (!url) {\r\n if (!editable) {\r\n return null;\r\n }\r\n return (\r\n <LinkPreviewUrlInput\r\n onSubmit={(newUrl) => {\r\n props.editor.updateBlock(props.block, {\r\n props: { url: newUrl },\r\n });\r\n }}\r\n />\r\n );\r\n }\r\n\r\n if (status === \"loading\") {\r\n return <LinkPreviewSkeleton />;\r\n }\r\n\r\n if (status === \"error\") {\r\n return <LinkPreviewError url={url} onRetry={handleRetry} />;\r\n }\r\n\r\n return (\r\n <LinkPreviewCard\r\n url={url}\r\n title={title}\r\n description={description}\r\n image={image}\r\n domain={domain || extractDomain(url)}\r\n editable={editable}\r\n onDelete={handleDelete}\r\n width={props.block.props.previewWidth}\r\n onWidthChange={(newWidth) => {\r\n props.editor.updateBlock(props.block, {\r\n props: { previewWidth: newWidth },\r\n });\r\n }}\r\n height={props.block.props.previewHeight}\r\n onHeightChange={(newHeight) => {\r\n props.editor.updateBlock(props.block, {\r\n props: { previewHeight: newHeight },\r\n });\r\n }}\r\n />\r\n );\r\n },\r\n }\r\n);\r\n","export const DEFAULT_LINK_PREVIEW_IMAGE = \"data:image/png;base64,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em1RHrY9EhgB5JWPq9DabLnDWKK8KrQqj2dIoXVQj+a8flLfBJN6T2FxKe//POeH76ctPvCWE/otMJ1HREREREREREREJJlUannNa4uGYNuPAV0rjavQ2my5w1iivCq0Ko9nSKGVFQzw84U706NFvQQTe9fcpetK3v1h9h3/N7TrVaaziIiIiIiIiIiIiCSLSi2veG1Rc+BubPvEKstUaG223GEsUV4VWpXHM6TQAjilfxueOKJngon9YdJfC+d+8du8g64+ZvufTGcRERERERERERERqS2VWl7w2qJTiBdaTaosU6G12XKHsUR5VWhVHs+gQis/J8jkS3elXaM8/G7l2iL77e9iYz79Ze7xz1y8R7HpPCIiIiIiIiIiIiI1pVIrk722yAIeA4ZvU6GhQqvyGxVaniq0AC7bI8Rtw7skmNyffpu5bNWnv8w96pLDtvvQdBYRERERERERERGRmlCplYnGLA5g22cAdwMNVGhVZy6HsUR5VWhVHs+wQqtxnRymX7krjepkJ9iBf63bUMy7381655XPZxzx+nX7FprOIyIiIiIiIiIiIrItVGplmjGLO2LbTwFDgG0rNFRoVX6jQstzhRY23LF/Fy7ZI5RgBwLwxz9LV3/ww+xjLj+677ums4iIiIiIiIiIiIhUl0qtTDJm8WnY9n1AA0CFVrXmchhLlFeFVuXxDCy02jeqw+TLd6FOdqUl4mDdhmI++H7WW+9/N+uIJy7dU8/aEhEREREREREREddTqZUJxixuDjyBbR9SPqZCS4XWFrP6r9ACeOqonpy0Y5sEOxEnv85YsvzTH2fvf+m/tv/adBYRERERERERERGRLVGp5XZjFh8EPIlttygfU6GlQmuLWf1ZaPVqWZ+fL9qZgP5U22Yr1xTab07659GTh3U/z3QWERERERERERERkUR0+tetxizOB+4FzqpRMbWl11tarkJrC/tQoVWtdR23cd5tsgotgDdP7ssBPZon2JFUx4Rf58a++XPBblce32+W6SwiIiIiIiIiIiIim1Op5UZjFm8HvAr0UqFVzXVVaPm60NqtQwGfn90/wY5kW8xbsrb4va9nnnvGQb2eMJ1FREREREREREREpCKVWm4zZvFZwH1AHRVa1VxXhZavC60ANhPP3YmBVqMEO5NtVVRcyrtfRz/86NvogY9ePqTYdB4RERERERERERER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\";\r\n","import { createReactBlockSpec } from \"@blocknote/react\";\r\nimport React, { useState, useCallback, useRef, useEffect } from \"react\";\r\n\r\nconst MIN_VIDEO_WIDTH = 200;\r\nconst MAX_VIDEO_WIDTH = 1200;\r\nconst MIN_VIDEO_HEIGHT = 120;\r\nconst MAX_VIDEO_HEIGHT = 600;\r\nconst DEFAULT_VIDEO_WIDTH = 640;\r\nconst DEFAULT_VIDEO_HEIGHT = 360;\r\n\r\ntype ResizeParams = {\r\n handleUsed: \"left\" | \"right\" | \"bottom\";\r\n initialClientX: number;\r\n initialClientY: number;\r\n initialWidth: number;\r\n initialHeight: number;\r\n};\r\n\r\n/**\r\n * 비디오 블록 카드: 우클릭 방지 + 다운로드 버튼 미노출\r\n * - controlsList=\"nodownload\": 브라우저 기본 컨트롤에서 다운로드 버튼 숨김\r\n * - onContextMenu preventDefault: 우클릭 컨텍스트 메뉴(동영상 저장 등) 차단\r\n */\r\nconst VideoBlockCard = ({\r\n url,\r\n editable,\r\n width,\r\n onWidthChange,\r\n height,\r\n onHeightChange,\r\n onContextMenuBlock,\r\n}: {\r\n url: string;\r\n editable: boolean;\r\n width?: number;\r\n onWidthChange?: (width: number) => void;\r\n height?: number;\r\n onHeightChange?: (height: number) => void;\r\n onContextMenuBlock: (e: React.MouseEvent) => void;\r\n}) => {\r\n const [resizeParams, setResizeParams] = useState<ResizeParams | undefined>(\r\n undefined,\r\n );\r\n const [localWidth, setLocalWidth] = useState<number | undefined>(width);\r\n const [localHeight, setLocalHeight] = useState<number | undefined>(height);\r\n const wrapperRef = useRef<HTMLDivElement>(null);\r\n\r\n useEffect(() => {\r\n setLocalWidth(width);\r\n }, [width]);\r\n useEffect(() => {\r\n setLocalHeight(height);\r\n }, [height]);\r\n\r\n useEffect(() => {\r\n if (!resizeParams) return;\r\n\r\n const onMouseMove = (e: MouseEvent) => {\r\n if (resizeParams.handleUsed === \"bottom\") {\r\n const delta = e.clientY - resizeParams.initialClientY;\r\n setLocalHeight(\r\n Math.min(\r\n Math.max(resizeParams.initialHeight + delta, MIN_VIDEO_HEIGHT),\r\n MAX_VIDEO_HEIGHT,\r\n ),\r\n );\r\n } else {\r\n const delta =\r\n resizeParams.handleUsed === \"left\"\r\n ? resizeParams.initialClientX - e.clientX\r\n : e.clientX - resizeParams.initialClientX;\r\n setLocalWidth(\r\n Math.min(\r\n Math.max(resizeParams.initialWidth + delta, MIN_VIDEO_WIDTH),\r\n wrapperRef.current?.parentElement?.clientWidth || MAX_VIDEO_WIDTH,\r\n ),\r\n );\r\n }\r\n };\r\n\r\n const onMouseUp = () => {\r\n const handle = resizeParams.handleUsed;\r\n setResizeParams(undefined);\r\n if (handle === \"bottom\") {\r\n if (localHeight != null && onHeightChange) onHeightChange(localHeight);\r\n } else {\r\n if (localWidth != null && onWidthChange) onWidthChange(localWidth);\r\n }\r\n };\r\n\r\n window.addEventListener(\"mousemove\", onMouseMove);\r\n window.addEventListener(\"mouseup\", onMouseUp);\r\n return () => {\r\n window.removeEventListener(\"mousemove\", onMouseMove);\r\n window.removeEventListener(\"mouseup\", onMouseUp);\r\n };\r\n }, [resizeParams, localWidth, localHeight, onWidthChange, onHeightChange]);\r\n\r\n const handleLeftDown = useCallback(\r\n (e: React.MouseEvent) => {\r\n e.preventDefault();\r\n e.stopPropagation();\r\n setResizeParams({\r\n handleUsed: \"left\",\r\n initialWidth: wrapperRef.current?.clientWidth ?? DEFAULT_VIDEO_WIDTH,\r\n initialHeight: localHeight ?? DEFAULT_VIDEO_HEIGHT,\r\n initialClientX: e.clientX,\r\n initialClientY: e.clientY,\r\n });\r\n },\r\n [localHeight],\r\n );\r\n\r\n const handleRightDown = useCallback(\r\n (e: React.MouseEvent) => {\r\n e.preventDefault();\r\n e.stopPropagation();\r\n setResizeParams({\r\n handleUsed: \"right\",\r\n initialWidth: wrapperRef.current?.clientWidth ?? DEFAULT_VIDEO_WIDTH,\r\n initialHeight: localHeight ?? DEFAULT_VIDEO_HEIGHT,\r\n initialClientX: e.clientX,\r\n initialClientY: e.clientY,\r\n });\r\n },\r\n [localHeight],\r\n );\r\n\r\n const handleBottomDown = useCallback(\r\n (e: React.MouseEvent) => {\r\n e.preventDefault();\r\n e.stopPropagation();\r\n setResizeParams({\r\n handleUsed: \"bottom\",\r\n initialWidth: wrapperRef.current?.clientWidth ?? DEFAULT_VIDEO_WIDTH,\r\n initialHeight: localHeight ?? DEFAULT_VIDEO_HEIGHT,\r\n initialClientX: e.clientX,\r\n initialClientY: e.clientY,\r\n });\r\n },\r\n [localHeight],\r\n );\r\n\r\n const resizeCursor = resizeParams\r\n ? resizeParams.handleUsed === \"bottom\"\r\n ? \"ns-resize\"\r\n : \"ew-resize\"\r\n : \"default\";\r\n\r\n const [hovered, setHovered] = useState(false);\r\n\r\n return (\r\n <div\r\n ref={wrapperRef}\r\n className=\"lumir-video-block-wrapper\"\r\n onContextMenu={onContextMenuBlock}\r\n style={{\r\n position: \"relative\",\r\n width: localWidth != null ? `${localWidth}px` : undefined,\r\n maxWidth: \"100%\",\r\n cursor: resizeCursor,\r\n transition: resizeParams ? \"none\" : \"box-shadow 0.2s\",\r\n }}\r\n onMouseEnter={() => {\r\n if (!resizeParams) setHovered(true);\r\n }}\r\n onMouseLeave={() => {\r\n if (!resizeParams) setHovered(false);\r\n }}\r\n >\r\n {editable && (hovered || resizeParams) && (\r\n <>\r\n <div\r\n className=\"lumir-resize-handle\"\r\n style={{ left: \"4px\" }}\r\n onMouseDown={handleLeftDown}\r\n />\r\n <div\r\n className=\"lumir-resize-handle\"\r\n style={{ right: \"4px\" }}\r\n onMouseDown={handleRightDown}\r\n />\r\n </>\r\n )}\r\n\r\n <div\r\n style={{\r\n width: \"100%\",\r\n height: `${localHeight ?? DEFAULT_VIDEO_HEIGHT}px`,\r\n overflow: \"hidden\",\r\n backgroundColor: \"#000\",\r\n borderRadius: \"6px\",\r\n }}\r\n >\r\n <video\r\n src={url}\r\n controls\r\n controlsList=\"nodownload\"\r\n playsInline\r\n style={{\r\n width: \"100%\",\r\n height: \"100%\",\r\n objectFit: \"contain\",\r\n display: \"block\",\r\n }}\r\n onContextMenu={(e) => e.preventDefault()}\r\n />\r\n {editable && (hovered || resizeParams) && (\r\n <div\r\n className=\"lumir-resize-handle-bottom\"\r\n onMouseDown={handleBottomDown}\r\n />\r\n )}\r\n </div>\r\n </div>\r\n );\r\n};\r\n\r\nexport const VideoBlock = createReactBlockSpec(\r\n {\r\n type: \"video\",\r\n propSchema: {\r\n url: { default: \"\" },\r\n previewWidth: { default: 640 },\r\n previewHeight: { default: 360 },\r\n },\r\n content: \"none\",\r\n },\r\n {\r\n render: (props) => {\r\n const url = props.block.props.url ?? \"\";\r\n const editable = props.editor.isEditable;\r\n\r\n const handleContextMenu = useCallback((e: React.MouseEvent) => {\r\n e.preventDefault();\r\n e.stopPropagation();\r\n }, []);\r\n\r\n if (!url) {\r\n return (\r\n <div\r\n className=\"lumir-video-block-placeholder\"\r\n onContextMenu={handleContextMenu}\r\n style={{\r\n width: \"100%\",\r\n maxWidth: `${DEFAULT_VIDEO_WIDTH}px`,\r\n height: `${DEFAULT_VIDEO_HEIGHT}px`,\r\n backgroundColor: \"#1a1a1a\",\r\n borderRadius: \"6px\",\r\n display: \"flex\",\r\n alignItems: \"center\",\r\n justifyContent: \"center\",\r\n color: \"rgba(255,255,255,0.5)\",\r\n fontSize: \"14px\",\r\n }}\r\n >\r\n 비디오 URL 없음\r\n </div>\r\n );\r\n }\r\n\r\n return (\r\n <VideoBlockCard\r\n url={url}\r\n editable={editable}\r\n width={props.block.props.previewWidth}\r\n onWidthChange={(newWidth) => {\r\n props.editor.updateBlock(props.block, {\r\n props: { previewWidth: newWidth },\r\n });\r\n }}\r\n height={props.block.props.previewHeight}\r\n onHeightChange={(newHeight) => {\r\n props.editor.updateBlock(props.block, {\r\n props: { previewHeight: newHeight },\r\n });\r\n }}\r\n onContextMenuBlock={handleContextMenu}\r\n />\r\n );\r\n },\r\n },\r\n);\r\n","import { createStronglyTypedTiptapNode } from \"@blocknote/core\";\r\nimport { columnNormalization } from \"./columnNormalization\";\r\nimport { columnDnd } from \"./columnDnd\";\r\n\r\n/**\r\n * 다단(컬럼) 레이아웃의 컨테이너 노드 `columnList`.\r\n *\r\n * @blocknote/xl-multi-column(AGPL)을 쓰지 않고, 코어가 이미 인지하는 노드 구조\r\n * (core/src/pm-nodes/README.md, MIT)를 직접 정의한다(라이선스 안전).\r\n * - 코어 UniqueID가 [\"blockContainer\",\"columnList\",\"column\"]에 id 부여,\r\n * blockToNode가 bnBlock 그룹 노드를 {id,...props,children}로 변환,\r\n * getBlockInfoFromPos가 그룹 기반 처리.\r\n * - nodeToBlock(직렬화)은 blockSchema에 스펙이 있어야 인식하므로, 이 노드를\r\n * createBlockSpecFromStronglyTypedTiptapNode로 감싸 schema에 등록한다(HtmlPreview.tsx).\r\n *\r\n * 스키마(README):\r\n * group: \"childContainer bnBlock blockGroupChild\"\r\n * content: \"column column+\" (최소 2단)\r\n */\r\nexport const ColumnList = createStronglyTypedTiptapNode({\r\n name: \"columnList\",\r\n group: \"childContainer bnBlock blockGroupChild\",\r\n content: \"column column+\",\r\n\r\n addAttributes() {\r\n return {\r\n // 블록별 중앙 세로 구분선 표시 여부(드래그핸들 메뉴에서 토글). data-divider로 렌더.\r\n showDivider: {\r\n default: false,\r\n parseHTML: (element) => element.getAttribute(\"data-divider\") === \"true\",\r\n renderHTML: (attributes) =>\r\n attributes.showDivider ? { \"data-divider\": \"true\" } : {},\r\n },\r\n };\r\n },\r\n\r\n parseHTML() {\r\n return [{ tag: 'div[data-node-type=\"columnList\"]' }];\r\n },\r\n\r\n renderHTML({ HTMLAttributes }) {\r\n const dom = document.createElement(\"div\");\r\n dom.className = \"bn-column-list\";\r\n dom.setAttribute(\"data-node-type\", \"columnList\");\r\n for (const [attribute, value] of Object.entries(HTMLAttributes)) {\r\n if (attribute !== \"class\") {\r\n dom.setAttribute(attribute, value as string);\r\n }\r\n }\r\n return { dom, contentDOM: dom };\r\n },\r\n\r\n // columnNormalization: 빈 컬럼/1단 columnList 자동 복구(unwrap).\r\n // columnDnd: 블록을 다른 블록 좌/우로 드롭하면 2단 컬럼 생성.\r\n addProseMirrorPlugins() {\r\n return [columnNormalization(), columnDnd()];\r\n },\r\n});\r\n","import { Plugin, PluginKey } from \"prosemirror-state\";\r\n\r\nexport const columnNormalizationKey = new PluginKey(\"lumirColumnNormalization\");\r\n\r\n/**\r\n * columnList 불변식을 유지하는 정규화 플러그인.\r\n *\r\n * 문제: 코어 removeBlocks/일반 편집은 column/columnList의 최소 content 규칙을 강제하지\r\n * 않는다 → 컬럼의 마지막 블록을 지우면 **빈 column**(content \"blockContainer+\" 위반),\r\n * 컬럼을 통째로 지우면 **1단 columnList**(content \"column column+\" 위반)가 되어 문서가\r\n * 깨지고 직렬화(nodeToBlock)가 오류날 수 있다.\r\n *\r\n * 해결: appendTransaction에서 매 docChanged마다 columnList를 검사해,\r\n * - 내용 있는 컬럼이 2개 미만이면 columnList를 **풀어서**(unwrap) 남은 컬럼들의\r\n * blockContainer들을 부모 blockGroup에 직접 펼친다(blockContainer는 blockGroupChild라 유효).\r\n * - 내용이 전혀 없으면 columnList를 제거한다.\r\n * 정규화 후에는 더 고칠 게 없어 self-terminating.\r\n */\r\nexport function columnNormalization(): Plugin {\r\n return new Plugin({\r\n key: columnNormalizationKey,\r\n appendTransaction(transactions, _oldState, newState) {\r\n if (!transactions.some((tr) => tr.docChanged)) {\r\n return null;\r\n }\r\n\r\n const lists: { pos: number; node: any }[] = [];\r\n newState.doc.descendants((node: any, pos: number) => {\r\n if (node.type.name === \"columnList\") {\r\n lists.push({ pos, node });\r\n return false; // columnList는 중첩되지 않음 → 하위 순회 불필요\r\n }\r\n return undefined;\r\n });\r\n if (lists.length === 0) {\r\n return null;\r\n }\r\n\r\n const tr = newState.tr;\r\n let modified = false;\r\n\r\n // 위치 큰 것부터 처리해 앞쪽 위치가 어긋나지 않게 한다.\r\n for (let i = lists.length - 1; i >= 0; i--) {\r\n const { pos, node } = lists[i];\r\n\r\n const nonEmptyColumns: any[] = [];\r\n node.forEach((col: any) => {\r\n if (col.type.name === \"column\" && col.childCount > 0) {\r\n nonEmptyColumns.push(col);\r\n }\r\n });\r\n\r\n if (nonEmptyColumns.length >= 2) {\r\n continue; // 유효한 다단 → 유지\r\n }\r\n\r\n const from = tr.mapping.map(pos);\r\n const to = tr.mapping.map(pos + node.nodeSize);\r\n\r\n // 남은 컬럼들의 blockContainer들을 모아 columnList를 대체(unwrap).\r\n const blocks: any[] = [];\r\n nonEmptyColumns.forEach((col: any) => {\r\n col.forEach((bc: any) => blocks.push(bc));\r\n });\r\n\r\n if (blocks.length > 0) {\r\n tr.replaceWith(from, to, blocks);\r\n } else {\r\n tr.delete(from, to);\r\n }\r\n modified = true;\r\n }\r\n\r\n return modified ? tr : null;\r\n },\r\n });\r\n}\r\n","import { Plugin, PluginKey } from \"prosemirror-state\";\r\nimport type { EditorView } from \"prosemirror-view\";\r\nimport { Decoration, DecorationSet } from \"prosemirror-view\";\r\nimport { mergeBlocksIntoColumns } from \"./mergeColumns\";\r\n\r\nexport const columnDndKey = new PluginKey<DndState>(\"lumirColumnDnd\");\r\n\r\ntype DndState = {\r\n /** 대상 top-level blockContainer의 pos(없으면 null). */\r\n targetPos: number | null;\r\n side: \"left\" | \"right\" | null;\r\n};\r\n\r\n/** 가장자리로 인식할 폭(px). 블록 폭의 30%와 80px 중 작은 값. */\r\nfunction edgeThreshold(width: number): number {\r\n return Math.min(80, width * 0.3);\r\n}\r\n\r\n/**\r\n * 좌표 아래의 **top-level** blockContainer(루트 blockGroup 직속)를 찾는다.\r\n * v1은 최상위 블록만 컬럼 생성 대상으로 허용(컬럼 내부/중첩 블록은 제외).\r\n */\r\nfunction topLevelBlockAtCoords(\r\n view: EditorView,\r\n x: number,\r\n y: number,\r\n): { pos: number; nodeSize: number; id: string; dom: HTMLElement } | null {\r\n const found = view.posAtCoords({ left: x, top: y });\r\n if (!found) {\r\n return null;\r\n }\r\n const $pos = view.state.doc.resolve(found.pos);\r\n for (let d = $pos.depth; d > 0; d--) {\r\n if ($pos.node(d).type.name === \"blockContainer\") {\r\n // 루트 blockGroup(depth 1) 직속만 → blockContainer는 depth 2.\r\n if (d !== 2 || $pos.node(d - 1).type.name !== \"blockGroup\") {\r\n return null;\r\n }\r\n const pos = $pos.before(d);\r\n const node = $pos.node(d);\r\n const dom = view.nodeDOM(pos) as HTMLElement | null;\r\n if (!dom || !node.attrs?.id) {\r\n return null;\r\n }\r\n return { pos, nodeSize: node.nodeSize, id: node.attrs.id, dom };\r\n }\r\n }\r\n return null;\r\n}\r\n\r\n/** NodeSelection이면 드래그된 단일 블록 id를 반환(다중 선택은 v1 미지원 → null). */\r\nfunction draggedBlockId(view: EditorView): string | null {\r\n const sel: any = view.state.selection;\r\n // MultipleNodeSelection 등은 node가 없음 → v1 단일만.\r\n return sel?.node?.attrs?.id ?? null;\r\n}\r\n\r\nfunction setDnd(view: EditorView, next: DndState) {\r\n const cur = columnDndKey.getState(view.state);\r\n if (cur && cur.targetPos === next.targetPos && cur.side === next.side) {\r\n return; // 변화 없음 → dispatch 생략(드래그오버 스팸 방지)\r\n }\r\n view.dispatch(view.state.tr.setMeta(columnDndKey, next));\r\n}\r\n\r\nconst CLEARED: DndState = { targetPos: null, side: null };\r\n\r\n/**\r\n * 블록을 다른 블록의 좌/우 가장자리로 드롭하면 2단 컬럼을 생성하는 DnD 플러그인.\r\n * - dragover: 대상 블록의 좌/우 가장자리 hit-test → 세로 인디케이터(데코) + 기본 드롭커서 숨김.\r\n * - drop(handleDrop): 가장자리 드롭이면 mergeBlocksIntoColumns로 래핑하고 기본 동작 취소.\r\n */\r\nexport function columnDnd(): Plugin<DndState> {\r\n return new Plugin<DndState>({\r\n key: columnDndKey,\r\n state: {\r\n init: () => CLEARED,\r\n apply(tr, prev) {\r\n const meta = tr.getMeta(columnDndKey);\r\n if (meta) {\r\n return meta as DndState;\r\n }\r\n // 문서가 바뀌면 좌표 기반 targetPos가 무효 → 초기화.\r\n if (tr.docChanged && prev.targetPos !== null) {\r\n return CLEARED;\r\n }\r\n return prev;\r\n },\r\n },\r\n props: {\r\n handleDOMEvents: {\r\n dragover: (view, event) => {\r\n if (!(view as any).dragging) {\r\n return false;\r\n }\r\n const target = topLevelBlockAtCoords(\r\n view,\r\n event.clientX,\r\n event.clientY,\r\n );\r\n const draggedId = draggedBlockId(view);\r\n if (!target || !draggedId || target.id === draggedId) {\r\n setDnd(view, CLEARED);\r\n view.dom.classList.remove(\"lumir-col-dnd-active\");\r\n return false;\r\n }\r\n const rect = target.dom.getBoundingClientRect();\r\n const th = edgeThreshold(rect.width);\r\n let side: \"left\" | \"right\" | null = null;\r\n if (event.clientX - rect.left <= th) {\r\n side = \"left\";\r\n } else if (rect.right - event.clientX <= th) {\r\n side = \"right\";\r\n }\r\n if (side) {\r\n setDnd(view, { targetPos: target.pos, side });\r\n view.dom.classList.add(\"lumir-col-dnd-active\");\r\n } else {\r\n setDnd(view, CLEARED);\r\n view.dom.classList.remove(\"lumir-col-dnd-active\");\r\n }\r\n return false;\r\n },\r\n dragend: (view) => {\r\n setDnd(view, CLEARED);\r\n view.dom.classList.remove(\"lumir-col-dnd-active\");\r\n return false;\r\n },\r\n dragleave: (view, event) => {\r\n // 에디터 밖으로 나가면 정리(내부 이동은 무시).\r\n if (!(event as DragEvent).relatedTarget) {\r\n setDnd(view, CLEARED);\r\n view.dom.classList.remove(\"lumir-col-dnd-active\");\r\n }\r\n return false;\r\n },\r\n },\r\n handleDrop: (view, _event, _slice, _moved) => {\r\n const st = columnDndKey.getState(view.state);\r\n view.dom.classList.remove(\"lumir-col-dnd-active\");\r\n if (!st || st.side === null || st.targetPos === null) {\r\n return false; // 일반(상하) 드롭 → 기본 처리\r\n }\r\n const draggedId = draggedBlockId(view);\r\n const targetNode = view.state.doc.nodeAt(st.targetPos);\r\n const targetId = targetNode?.attrs?.id as string | undefined;\r\n // 상태 초기화(드롭 후 잔여 인디케이터 제거)\r\n view.dispatch(view.state.tr.setMeta(columnDndKey, CLEARED));\r\n if (!draggedId || !targetId) {\r\n return false;\r\n }\r\n const editor = (view as any).__lumirEditor;\r\n if (!editor) {\r\n return false;\r\n }\r\n const ok = mergeBlocksIntoColumns(editor, draggedId, targetId, st.side);\r\n return ok; // 성공 시 기본 드롭 취소\r\n },\r\n decorations: (state) => {\r\n const st = columnDndKey.getState(state);\r\n if (!st || st.side === null || st.targetPos === null) {\r\n return null;\r\n }\r\n const node = state.doc.nodeAt(st.targetPos);\r\n if (!node) {\r\n return null;\r\n }\r\n return DecorationSet.create(state.doc, [\r\n Decoration.node(st.targetPos, st.targetPos + node.nodeSize, {\r\n class:\r\n st.side === \"left\"\r\n ? \"lumir-col-drop-left\"\r\n : \"lumir-col-drop-right\",\r\n }),\r\n ]);\r\n },\r\n },\r\n });\r\n}\r\n","/**\r\n * 드래그된 블록과 대상 블록을 2단 columnList로 병합한다(DnD 컬럼 생성의 핵심 로직).\r\n *\r\n * 공개 블록 API(getBlock/removeBlocks/replaceBlocks)만 사용하므로 columnList/column이\r\n * 블록 스펙으로 등록돼 있어야 한다(이미 등록됨). 이벤트 처리(columnDnd 플러그인)와 분리해\r\n * 단독 테스트가 가능하다.\r\n *\r\n * @param side \"left\" → [dragged | target], \"right\" → [target | dragged]\r\n * @returns 병합 성공 여부\r\n */\r\nexport function mergeBlocksIntoColumns(\r\n editor: any,\r\n draggedId: string,\r\n targetId: string,\r\n side: \"left\" | \"right\",\r\n): boolean {\r\n if (!editor || !draggedId || !targetId || draggedId === targetId) {\r\n return false;\r\n }\r\n\r\n const dragged = editor.getBlock?.(draggedId);\r\n const target = editor.getBlock?.(targetId);\r\n if (!dragged || !target) {\r\n return false;\r\n }\r\n // 컬럼/컬럼리스트 자체는 드래그 대상에서 제외(중첩/해체 복잡도 회피, v1).\r\n if (\r\n dragged.type === \"columnList\" ||\r\n dragged.type === \"column\" ||\r\n target.type === \"columnList\" ||\r\n target.type === \"column\"\r\n ) {\r\n return false;\r\n }\r\n\r\n const columnList = {\r\n type: \"columnList\",\r\n children:\r\n side === \"left\"\r\n ? [\r\n { type: \"column\", children: [dragged] },\r\n { type: \"column\", children: [target] },\r\n ]\r\n : [\r\n { type: \"column\", children: [target] },\r\n { type: \"column\", children: [dragged] },\r\n ],\r\n };\r\n\r\n try {\r\n const run = () => {\r\n // 원본에서 드래그 블록 제거 후, 대상을 columnList로 교체(대상+드래그를 단으로).\r\n editor.removeBlocks([draggedId]);\r\n editor.replaceBlocks([targetId], [columnList]);\r\n };\r\n if (typeof editor.transact === \"function\") {\r\n editor.transact(run);\r\n } else {\r\n run();\r\n }\r\n return true;\r\n } catch {\r\n return false;\r\n }\r\n}\r\n","import { createStronglyTypedTiptapNode } from \"@blocknote/core\";\r\n\r\n/**\r\n * 다단 레이아웃의 단일 컬럼 노드 `column`.\r\n *\r\n * 스키마(core/src/pm-nodes/README.md, MIT):\r\n * group: \"bnBlock childContainer\"\r\n * content: \"blockContainer+\" (컬럼은 1개 이상의 블록을 담는다)\r\n *\r\n * MVP은 모든 컬럼을 균등 폭(flex:1)으로 렌더한다. 향후 너비 리사이즈를 위해 선택적\r\n * `width` attr 자리만 둔다(현재 CSS 미사용).\r\n */\r\nexport const Column = createStronglyTypedTiptapNode({\r\n name: \"column\",\r\n group: \"bnBlock childContainer\",\r\n content: \"blockContainer+\",\r\n\r\n addAttributes() {\r\n return {\r\n width: {\r\n default: 1,\r\n parseHTML: (element) => {\r\n const w = parseFloat(element.getAttribute(\"data-column-width\") || \"\");\r\n return Number.isFinite(w) && w > 0 ? w : 1;\r\n },\r\n renderHTML: (attributes) => {\r\n if (!attributes.width || attributes.width === 1) {\r\n return {};\r\n }\r\n return { \"data-column-width\": String(attributes.width) };\r\n },\r\n },\r\n };\r\n },\r\n\r\n parseHTML() {\r\n return [{ tag: 'div[data-node-type=\"column\"]' }];\r\n },\r\n\r\n renderHTML({ HTMLAttributes }) {\r\n const dom = document.createElement(\"div\");\r\n dom.className = \"bn-column\";\r\n dom.setAttribute(\"data-node-type\", \"column\");\r\n for (const [attribute, value] of Object.entries(HTMLAttributes)) {\r\n if (attribute !== \"class\") {\r\n dom.setAttribute(attribute, value as string);\r\n }\r\n }\r\n return { dom, contentDOM: dom };\r\n },\r\n});\r\n","\"use client\";\r\n\r\nimport { createReactStyleSpec } from \"@blocknote/react\";\r\n\r\n/**\r\n * 인라인 글자 크기 커스텀 스타일.\r\n *\r\n * - propSchema \"string\": 값으로 \"18px\" 같은 CSS 크기를 가진다.\r\n * - HTML 직렬화: `<span data-style-type=\"fontSize\" data-value=\"18px\" style=\"font-size:18px\">`\r\n * (BlockNote가 data-* 속성과 파싱 규칙을 자동 생성 → 신버전 에디터 간 복사/붙여넣기 왕복 보장,\r\n * 구버전 에디터는 매칭 파싱 규칙이 없어 조용히 무시)\r\n *\r\n * ⚠️ 저장 JSON 하위호환: BlockNote는 styles 맵에 스키마에 없는 키가 있으면\r\n * `style ... not found in styleSchema` 예외를 던지므로, 저장 JSON에는\r\n * styles.fontSize 대신 형제 키 fontSize로 직렬화한다.\r\n * → src/utils/font-size-serialization.ts (liftFontSize/lowerFontSize) 참고.\r\n */\r\nexport const FontSize = createReactStyleSpec(\r\n {\r\n type: \"fontSize\",\r\n propSchema: \"string\",\r\n },\r\n {\r\n render: (props) => (\r\n <span style={{ fontSize: props.value }} ref={props.contentRef} />\r\n ),\r\n },\r\n);\r\n\r\n/** 툴바 드롭다운에서 제공하는 프리셋 (본문 기본은 14px = \"기본\") */\r\nexport const FONT_SIZE_PRESETS = [\r\n \"10px\",\r\n \"12px\",\r\n \"14px\",\r\n \"16px\",\r\n \"18px\",\r\n \"20px\",\r\n \"24px\",\r\n \"28px\",\r\n] as const;\r\n\r\nexport type FontSizePreset = (typeof FONT_SIZE_PRESETS)[number];\r\n","\"use client\";\r\n\r\nimport React, { useState, useEffect, useRef } from \"react\";\r\nimport type { EditorType } from \"../../types\";\r\nimport { cn } from \"../../utils/cn\";\r\nimport { Icons } from \"./Icons\";\r\nimport {\r\n ToolbarDivider,\r\n UndoRedoButtons,\r\n TextStyleButton,\r\n AlignButton,\r\n ListButton,\r\n ImageButton,\r\n ColorButton,\r\n FontSizeButton,\r\n LinkButton,\r\n TableButton,\r\n HTMLImportButton,\r\n BlockTypeSelect,\r\n} from \"./components\";\r\n\r\n// 반응형 브레이크포인트 (px)\r\nconst COMPACT_BREAKPOINT = 700;\r\nconst MINIMIZED_BREAKPOINT = 400;\r\n\r\nexport interface FloatingMenuProps {\r\n editor: EditorType | any;\r\n position?: \"sticky\" | \"fixed\";\r\n className?: string;\r\n onImageUpload?: () => void;\r\n}\r\n\r\n/**\r\n * FloatingMenu - 에디터 상단 고정 툴바\r\n */\r\nexport const FloatingMenu: React.FC<FloatingMenuProps> = ({\r\n editor,\r\n position = \"sticky\",\r\n className,\r\n onImageUpload,\r\n}) => {\r\n const wrapperRef = useRef<HTMLDivElement>(null);\r\n const [isCompact, setIsCompact] = useState(false);\r\n const [isMinimizable, setIsMinimizable] = useState(false);\r\n const [isMinimized, setIsMinimized] = useState(false);\r\n // 선택 변경 시 리렌더링을 위한 카운터\r\n const [, setSelectionTick] = useState(0);\r\n\r\n // 선택 변경 감지 - 스타일 버튼 상태 업데이트를 위해 (디바운싱 적용)\r\n useEffect(() => {\r\n if (!editor) return;\r\n\r\n let debounceTimer: ReturnType<typeof setTimeout> | null = null;\r\n const DEBOUNCE_DELAY = 150;\r\n\r\n const handleSelectionChange = () => {\r\n if (debounceTimer) {\r\n clearTimeout(debounceTimer);\r\n }\r\n debounceTimer = setTimeout(() => {\r\n setSelectionTick((prev) => prev + 1);\r\n }, DEBOUNCE_DELAY);\r\n };\r\n\r\n const unsubscribe = editor.onSelectionChange?.(handleSelectionChange);\r\n const unsubscribeContent = editor.onEditorContentChange?.(() => {\r\n setSelectionTick((prev) => prev + 1);\r\n });\r\n\r\n return () => {\r\n if (debounceTimer) {\r\n clearTimeout(debounceTimer);\r\n }\r\n unsubscribe?.();\r\n unsubscribeContent?.();\r\n };\r\n }, [editor]);\r\n\r\n // 컨테이너 너비 감지\r\n useEffect(() => {\r\n const checkWidth = () => {\r\n if (wrapperRef.current) {\r\n const width = wrapperRef.current.offsetWidth;\r\n setIsCompact(width < COMPACT_BREAKPOINT);\r\n setIsMinimizable(width < MINIMIZED_BREAKPOINT);\r\n }\r\n };\r\n\r\n checkWidth();\r\n\r\n const resizeObserver = new ResizeObserver(checkWidth);\r\n if (wrapperRef.current) {\r\n resizeObserver.observe(wrapperRef.current);\r\n }\r\n\r\n return () => resizeObserver.disconnect();\r\n }, []);\r\n\r\n // 최소화된 레이아웃 (400px 이하)\r\n const MinimizedLayout = () => (\r\n <>\r\n <button\r\n className=\"lumir-toolbar-button lumir-toggle-button\"\r\n onClick={() => setIsMinimized(!isMinimized)}\r\n onMouseDown={(e) => e.preventDefault()}\r\n type=\"button\"\r\n title={isMinimized ? \"메뉴 펼치기\" : \"메뉴 접기\"}\r\n >\r\n {isMinimized ? Icons.chevronRight : Icons.chevronLeft}\r\n </button>\r\n {!isMinimized && (\r\n <>\r\n <ToolbarDivider />\r\n <UndoRedoButtons editor={editor} />\r\n <ToolbarDivider />\r\n <div className=\"lumir-toolbar-group\">\r\n <BlockTypeSelect editor={editor} />\r\n </div>\r\n <ToolbarDivider />\r\n <div className=\"lumir-toolbar-group\">\r\n <TextStyleButton editor={editor} style=\"bold\" />\r\n <TextStyleButton editor={editor} style=\"italic\" />\r\n <TextStyleButton editor={editor} style=\"underline\" />\r\n <TextStyleButton editor={editor} style=\"strike\" />\r\n </div>\r\n <ToolbarDivider />\r\n <div className=\"lumir-toolbar-group\">\r\n <AlignButton editor={editor} alignment=\"left\" />\r\n <AlignButton editor={editor} alignment=\"center\" />\r\n <AlignButton editor={editor} alignment=\"right\" />\r\n </div>\r\n <ToolbarDivider />\r\n <div className=\"lumir-toolbar-group\">\r\n <ListButton editor={editor} type=\"bullet\" />\r\n <ListButton editor={editor} type=\"numbered\" />\r\n </div>\r\n <ToolbarDivider />\r\n <div className=\"lumir-toolbar-group\">\r\n <FontSizeButton editor={editor} />\r\n <ColorButton editor={editor} type=\"text\" />\r\n <ColorButton editor={editor} type=\"background\" />\r\n </div>\r\n <ToolbarDivider />\r\n <div className=\"lumir-toolbar-group\">\r\n <ImageButton editor={editor} onImageUpload={onImageUpload} />\r\n <LinkButton editor={editor} />\r\n <TableButton editor={editor} />\r\n <HTMLImportButton editor={editor} />\r\n </div>\r\n </>\r\n )}\r\n </>\r\n );\r\n\r\n // 1단 레이아웃\r\n const SingleRowLayout = () => (\r\n <>\r\n <UndoRedoButtons editor={editor} />\r\n <ToolbarDivider />\r\n <div className=\"lumir-toolbar-group\">\r\n <BlockTypeSelect editor={editor} />\r\n </div>\r\n <ToolbarDivider />\r\n <div className=\"lumir-toolbar-group\">\r\n <TextStyleButton editor={editor} style=\"bold\" />\r\n <TextStyleButton editor={editor} style=\"italic\" />\r\n <TextStyleButton editor={editor} style=\"underline\" />\r\n <TextStyleButton editor={editor} style=\"strike\" />\r\n </div>\r\n <ToolbarDivider />\r\n <div className=\"lumir-toolbar-group\">\r\n <AlignButton editor={editor} alignment=\"left\" />\r\n <AlignButton editor={editor} alignment=\"center\" />\r\n <AlignButton editor={editor} alignment=\"right\" />\r\n </div>\r\n <ToolbarDivider />\r\n <div className=\"lumir-toolbar-group\">\r\n <ListButton editor={editor} type=\"bullet\" />\r\n <ListButton editor={editor} type=\"numbered\" />\r\n </div>\r\n <ToolbarDivider />\r\n <div className=\"lumir-toolbar-group\">\r\n <FontSizeButton editor={editor} />\r\n <ColorButton editor={editor} type=\"text\" />\r\n <ColorButton editor={editor} type=\"background\" />\r\n </div>\r\n <ToolbarDivider />\r\n <div className=\"lumir-toolbar-group\">\r\n <ImageButton editor={editor} onImageUpload={onImageUpload} />\r\n <LinkButton editor={editor} />\r\n <TableButton editor={editor} />\r\n <HTMLImportButton editor={editor} />\r\n </div>\r\n </>\r\n );\r\n\r\n // 2단 레이아웃\r\n const TwoRowLayout = () => (\r\n <>\r\n <div className=\"lumir-toolbar-row\">\r\n <UndoRedoButtons editor={editor} />\r\n <ToolbarDivider />\r\n <div className=\"lumir-toolbar-group\">\r\n <TextStyleButton editor={editor} style=\"bold\" />\r\n <TextStyleButton editor={editor} style=\"italic\" />\r\n <TextStyleButton editor={editor} style=\"underline\" />\r\n <TextStyleButton editor={editor} style=\"strike\" />\r\n </div>\r\n <ToolbarDivider />\r\n <div className=\"lumir-toolbar-group\">\r\n <AlignButton editor={editor} alignment=\"left\" />\r\n <AlignButton editor={editor} alignment=\"center\" />\r\n <AlignButton editor={editor} alignment=\"right\" />\r\n </div>\r\n <ToolbarDivider />\r\n <div className=\"lumir-toolbar-group\">\r\n <ListButton editor={editor} type=\"bullet\" />\r\n <ListButton editor={editor} type=\"numbered\" />\r\n </div>\r\n </div>\r\n <div className=\"lumir-toolbar-row\">\r\n <div className=\"lumir-toolbar-group\">\r\n <BlockTypeSelect editor={editor} />\r\n </div>\r\n <ToolbarDivider />\r\n <div className=\"lumir-toolbar-group\">\r\n <FontSizeButton editor={editor} />\r\n <ColorButton editor={editor} type=\"text\" />\r\n <ColorButton editor={editor} type=\"background\" />\r\n </div>\r\n <ToolbarDivider />\r\n <div className=\"lumir-toolbar-group\">\r\n <ImageButton editor={editor} onImageUpload={onImageUpload} />\r\n <LinkButton editor={editor} />\r\n <TableButton editor={editor} />\r\n <HTMLImportButton editor={editor} />\r\n </div>\r\n </div>\r\n </>\r\n );\r\n\r\n return (\r\n <div\r\n ref={wrapperRef}\r\n className={cn(\r\n \"lumir-floating-toolbar-wrapper\",\r\n isMinimizable && \"is-minimizable\",\r\n className\r\n )}\r\n data-position={position}\r\n >\r\n <div\r\n className={cn(\r\n \"lumir-floating-toolbar\",\r\n isCompact && \"is-compact\",\r\n isMinimizable && \"is-minimizable\",\r\n isMinimized && \"is-minimized\"\r\n )}\r\n >\r\n {isMinimizable ? (\r\n <MinimizedLayout />\r\n ) : isCompact ? (\r\n <TwoRowLayout />\r\n ) : (\r\n <SingleRowLayout />\r\n )}\r\n </div>\r\n </div>\r\n );\r\n};\r\n\r\n// 하위 호환성을 위한 default export\r\nexport default FloatingMenu;\r\n","\"use client\";\r\n\r\nimport React from \"react\";\r\n\r\n/**\r\n * FloatingMenu에서 사용하는 SVG 아이콘 컴포넌트들\r\n */\r\nexport const Icons = {\r\n undo: (\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" width=\"20\" height=\"20\">\r\n <path d=\"M12.5 8c-2.65 0-5.05.99-6.9 2.6L2 7v9h9l-3.62-3.62c1.39-1.16 3.16-1.88 5.12-1.88 3.54 0 6.55 2.31 7.6 5.5l2.37-.78C21.08 11.03 17.15 8 12.5 8z\" />\r\n </svg>\r\n ),\r\n redo: (\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" width=\"20\" height=\"20\">\r\n <path d=\"M18.4 10.6C16.55 8.99 14.15 8 11.5 8c-4.65 0-8.58 3.03-9.96 7.22L3.9 16c1.05-3.19 4.05-5.5 7.6-5.5 1.95 0 3.73.72 5.12 1.88L13 16h9V7l-3.6 3.6z\" />\r\n </svg>\r\n ),\r\n bold: (\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" width=\"20\" height=\"20\">\r\n <path d=\"M15.6 10.79c.97-.67 1.65-1.77 1.65-2.79 0-2.26-1.75-4-4-4H7v14h7.04c2.09 0 3.71-1.7 3.71-3.79 0-1.52-.86-2.82-2.15-3.42zM10 6.5h3c.83 0 1.5.67 1.5 1.5s-.67 1.5-1.5 1.5h-3v-3zm3.5 9H10v-3h3.5c.83 0 1.5.67 1.5 1.5s-.67 1.5-1.5 1.5z\" />\r\n </svg>\r\n ),\r\n italic: (\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" width=\"20\" height=\"20\">\r\n <path d=\"M10 4v3h2.21l-3.42 8H6v3h8v-3h-2.21l3.42-8H18V4z\" />\r\n </svg>\r\n ),\r\n underline: (\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" width=\"20\" height=\"20\">\r\n <path d=\"M12 17c3.31 0 6-2.69 6-6V3h-2.5v8c0 1.93-1.57 3.5-3.5 3.5S8.5 12.93 8.5 11V3H6v8c0 3.31 2.69 6 6 6zm-7 2v2h14v-2H5z\" />\r\n </svg>\r\n ),\r\n strikethrough: (\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" width=\"20\" height=\"20\">\r\n <path d=\"M10 19h4v-3h-4v3zM5 4v3h5v3h4V7h5V4H5zM3 14h18v-2H3v2z\" />\r\n </svg>\r\n ),\r\n alignLeft: (\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" width=\"20\" height=\"20\">\r\n <path d=\"M15 15H3v2h12v-2zm0-8H3v2h12V7zM3 13h18v-2H3v2zm0 8h18v-2H3v2zM3 3v2h18V3H3z\" />\r\n </svg>\r\n ),\r\n alignCenter: (\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" width=\"20\" height=\"20\">\r\n <path d=\"M7 15v2h10v-2H7zm-4 6h18v-2H3v2zm0-8h18v-2H3v2zm4-6v2h10V7H7zM3 3v2h18V3H3z\" />\r\n </svg>\r\n ),\r\n alignRight: (\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" width=\"20\" height=\"20\">\r\n <path d=\"M3 21h18v-2H3v2zm6-4h12v-2H9v2zm-6-4h18v-2H3v2zm6-4h12V7H9v2zM3 3v2h18V3H3z\" />\r\n </svg>\r\n ),\r\n bulletList: (\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" width=\"20\" height=\"20\">\r\n <path d=\"M4 10.5c-.83 0-1.5.67-1.5 1.5s.67 1.5 1.5 1.5 1.5-.67 1.5-1.5-.67-1.5-1.5-1.5zm0-6c-.83 0-1.5.67-1.5 1.5S3.17 7.5 4 7.5 5.5 6.83 5.5 6 4.83 4.5 4 4.5zm0 12c-.83 0-1.5.68-1.5 1.5s.68 1.5 1.5 1.5 1.5-.68 1.5-1.5-.67-1.5-1.5-1.5zM7 19h14v-2H7v2zm0-6h14v-2H7v2zm0-8v2h14V5H7z\" />\r\n </svg>\r\n ),\r\n numberedList: (\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" width=\"20\" height=\"20\">\r\n <path d=\"M2 17h2v.5H3v1h1v.5H2v1h3v-4H2v1zm1-9h1V4H2v1h1v3zm-1 3h1.8L2 13.1v.9h3v-1H3.2L5 10.9V10H2v1zm5-6v2h14V5H7zm0 14h14v-2H7v2zm0-6h14v-2H7v2z\" />\r\n </svg>\r\n ),\r\n image: (\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" width=\"20\" height=\"20\">\r\n <path d=\"M21 19V5c0-1.1-.9-2-2-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2zM8.5 13.5l2.5 3.01L14.5 12l4.5 6H5l3.5-4.5z\" />\r\n </svg>\r\n ),\r\n expandMore: (\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" width=\"18\" height=\"18\">\r\n <path d=\"M16.59 8.59L12 13.17 7.41 8.59 6 10l6 6 6-6z\" />\r\n </svg>\r\n ),\r\n textColor: (\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" width=\"20\" height=\"20\">\r\n <path d=\"M11 3L5.5 17h2.25l1.12-3h6.25l1.12 3h2.25L13 3h-2zm-1.38 9L12 5.67 14.38 12H9.62z\" />\r\n </svg>\r\n ),\r\n bgColor: (\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" width=\"20\" height=\"20\">\r\n <path d=\"M16.56 8.94L7.62 0 6.21 1.41l2.38 2.38-5.15 5.15c-.59.59-.59 1.54 0 2.12l5.5 5.5c.29.29.68.44 1.06.44s.77-.15 1.06-.44l5.5-5.5c.59-.58.59-1.53 0-2.12zM5.21 10L10 5.21 14.79 10H5.21zM19 11.5s-2 2.17-2 3.5c0 1.1.9 2 2 2s2-.9 2-2c0-1.33-2-3.5-2-3.5z\" />\r\n <path fillOpacity=\".36\" d=\"M0 20h24v4H0z\" />\r\n </svg>\r\n ),\r\n link: (\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" width=\"20\" height=\"20\">\r\n <path d=\"M3.9 12c0-1.71 1.39-3.1 3.1-3.1h4V7H7c-2.76 0-5 2.24-5 5s2.24 5 5 5h4v-1.9H7c-1.71 0-3.1-1.39-3.1-3.1zM8 13h8v-2H8v2zm9-6h-4v1.9h4c1.71 0 3.1 1.39 3.1 3.1s-1.39 3.1-3.1 3.1h-4V17h4c2.76 0 5-2.24 5-5s-2.24-5-5-5z\" />\r\n </svg>\r\n ),\r\n chevronRight: (\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" width=\"20\" height=\"20\">\r\n <path d=\"M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z\" />\r\n </svg>\r\n ),\r\n chevronLeft: (\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" width=\"20\" height=\"20\">\r\n <path d=\"M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z\" />\r\n </svg>\r\n ),\r\n table: (\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" width=\"20\" height=\"20\">\r\n <path d=\"M20 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h15c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zM10 17H5v-2h5v2zm0-4H5v-2h5v2zm0-4H5V7h5v2zm9 8h-7v-2h7v2zm0-4h-7v-2h7v2zm0-4h-7V7h7v2z\" />\r\n </svg>\r\n ),\r\n htmlFile: (\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" width=\"20\" height=\"20\">\r\n <path d=\"M14 2H6c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V8l-6-6zm-1 2v5h5l-5-5zm-4 14H7v-1h2v1zm0-2H7v-1h2v1zm-2-2h2v1H7v-1zm4 4h-2v-1h2v1zm0-2h-2v-1h2v1zm0-2h-2v-1h2v1zm6 4h-4v-1h4v1zm0-2h-4v-1h4v1zm0-2h-4v-1h4v1z\" />\r\n </svg>\r\n ),\r\n};\r\n\r\n/**\r\n * 블록 타입 아이콘들\r\n */\r\nexport const BlockTypeIcons: Record<string, React.ReactNode> = {\r\n paragraph: (\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" width=\"20\" height=\"20\">\r\n <path d=\"M5 5h14v2H5zM5 11h14v2H5zM5 17h10v2H5z\" />\r\n </svg>\r\n ),\r\n h1: <span className=\"lumir-block-icon-text\">H1</span>,\r\n h2: <span className=\"lumir-block-icon-text\">H2</span>,\r\n h3: <span className=\"lumir-block-icon-text\">H3</span>,\r\n h4: <span className=\"lumir-block-icon-text\">H4</span>,\r\n h5: <span className=\"lumir-block-icon-text\">H5</span>,\r\n h6: <span className=\"lumir-block-icon-text\">H6</span>,\r\n toggleH1: (\r\n <span className=\"lumir-block-icon-toggle\">\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" width=\"8\" height=\"8\">\r\n <path d=\"M8 5v14l11-7z\" />\r\n </svg>\r\n <span>H1</span>\r\n </span>\r\n ),\r\n toggleH2: (\r\n <span className=\"lumir-block-icon-toggle\">\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" width=\"8\" height=\"8\">\r\n <path d=\"M8 5v14l11-7z\" />\r\n </svg>\r\n <span>H2</span>\r\n </span>\r\n ),\r\n toggleH3: (\r\n <span className=\"lumir-block-icon-toggle\">\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" width=\"8\" height=\"8\">\r\n <path d=\"M8 5v14l11-7z\" />\r\n </svg>\r\n <span>H3</span>\r\n </span>\r\n ),\r\n quote: (\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" width=\"20\" height=\"20\">\r\n <path d=\"M6 17h3l2-4V7H5v6h3zm8 0h3l2-4V7h-6v6h3z\" />\r\n </svg>\r\n ),\r\n codeBlock: (\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" width=\"20\" height=\"20\">\r\n <path d=\"M9.4 16.6L4.8 12l4.6-4.6L8 6l-6 6 6 6 1.4-1.4zm5.2 0l4.6-4.6-4.6-4.6L16 6l6 6-6 6-1.4-1.4z\" />\r\n </svg>\r\n ),\r\n toggleList: (\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" width=\"20\" height=\"20\">\r\n <path d=\"M10 6h10v2H10zM10 11h10v2H10zM10 16h10v2H10z\" />\r\n <path\r\n d=\"M4 8l4 4-4 4\"\r\n stroke=\"currentColor\"\r\n strokeWidth=\"2\"\r\n fill=\"none\"\r\n strokeLinecap=\"round\"\r\n strokeLinejoin=\"round\"\r\n />\r\n </svg>\r\n ),\r\n bulletList: (\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" width=\"20\" height=\"20\">\r\n <circle cx=\"4\" cy=\"6\" r=\"1.5\" />\r\n <circle cx=\"4\" cy=\"12\" r=\"1.5\" />\r\n <circle cx=\"4\" cy=\"18\" r=\"1.5\" />\r\n <path d=\"M8 5h12v2H8zM8 11h12v2H8zM8 17h12v2H8z\" />\r\n </svg>\r\n ),\r\n numberedList: (\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" width=\"20\" height=\"20\">\r\n <path d=\"M2 17h2v.5H3v1h1v.5H2v1h3v-4H2v1zm1-9h1V4H2v1h1v3zm-1 3h1.8L2 13.1v.9h3v-1H3.2L5 10.9V10H2v1zm5-6v2h14V5H7zm0 14h14v-2H7v2zm0-6h14v-2H7v2z\" />\r\n </svg>\r\n ),\r\n checkList: (\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" width=\"20\" height=\"20\">\r\n <rect\r\n x=\"3\"\r\n y=\"4\"\r\n width=\"6\"\r\n height=\"6\"\r\n rx=\"1\"\r\n fill=\"none\"\r\n stroke=\"currentColor\"\r\n strokeWidth=\"1.5\"\r\n />\r\n <path\r\n d=\"M4.5 7l1.5 1.5 3-3\"\r\n stroke=\"currentColor\"\r\n strokeWidth=\"1.5\"\r\n fill=\"none\"\r\n strokeLinecap=\"round\"\r\n strokeLinejoin=\"round\"\r\n />\r\n <path d=\"M12 6h8v2h-8z\" />\r\n <rect\r\n x=\"3\"\r\n y=\"14\"\r\n width=\"6\"\r\n height=\"6\"\r\n rx=\"1\"\r\n fill=\"none\"\r\n stroke=\"currentColor\"\r\n strokeWidth=\"1.5\"\r\n />\r\n <path d=\"M12 16h8v2h-8z\" />\r\n </svg>\r\n ),\r\n};\r\n","\"use client\";\r\n\r\nimport React from \"react\";\r\n\r\n/**\r\n * 툴바 구분선 컴포넌트\r\n */\r\nexport const ToolbarDivider: React.FC = () => (\r\n <div className=\"lumir-toolbar-divider\" />\r\n);\r\n","\"use client\";\r\n\r\nimport React, { useCallback } from \"react\";\r\nimport type { EditorType } from \"../../../types\";\r\nimport { Icons } from \"../Icons\";\r\n\r\ninterface UndoRedoButtonsProps {\r\n editor: EditorType | any;\r\n}\r\n\r\n/**\r\n * 실행 취소 / 다시 실행 버튼 컴포넌트\r\n */\r\nexport const UndoRedoButtons: React.FC<UndoRedoButtonsProps> = ({ editor }) => {\r\n const handleUndo = useCallback(() => {\r\n try {\r\n editor?.undo?.();\r\n } catch (err) {\r\n console.error(\"Undo failed:\", err);\r\n }\r\n }, [editor]);\r\n\r\n const handleRedo = useCallback(() => {\r\n try {\r\n editor?.redo?.();\r\n } catch (err) {\r\n console.error(\"Redo failed:\", err);\r\n }\r\n }, [editor]);\r\n\r\n // 버튼 클릭 시 에디터 포커스/선택 영역 유지\r\n const handleMouseDown = useCallback((e: React.MouseEvent) => {\r\n e.preventDefault();\r\n }, []);\r\n\r\n return (\r\n <div className=\"lumir-toolbar-group\">\r\n <button\r\n className=\"lumir-toolbar-btn\"\r\n onClick={handleUndo}\r\n onMouseDown={handleMouseDown}\r\n title=\"실행 취소\"\r\n type=\"button\"\r\n >\r\n {Icons.undo}\r\n </button>\r\n <button\r\n className=\"lumir-toolbar-btn\"\r\n onClick={handleRedo}\r\n onMouseDown={handleMouseDown}\r\n title=\"다시 실행\"\r\n type=\"button\"\r\n >\r\n {Icons.redo}\r\n </button>\r\n </div>\r\n );\r\n};\r\n","\"use client\";\r\n\r\nimport React, { useCallback } from \"react\";\r\nimport type { EditorType } from \"../../../types\";\r\nimport { Icons } from \"../Icons\";\r\nimport { cn } from \"../../../utils/cn\";\r\n\r\ntype TextStyle = \"bold\" | \"italic\" | \"underline\" | \"strike\";\r\n\r\ninterface TextStyleButtonProps {\r\n editor: EditorType | any;\r\n style: TextStyle;\r\n}\r\n\r\nconst iconMap: Record<TextStyle, React.ReactNode> = {\r\n bold: Icons.bold,\r\n italic: Icons.italic,\r\n underline: Icons.underline,\r\n strike: Icons.strikethrough,\r\n};\r\n\r\nconst titleMap: Record<TextStyle, string> = {\r\n bold: \"굵게\",\r\n italic: \"기울임\",\r\n underline: \"밑줄\",\r\n strike: \"취소선\",\r\n};\r\n\r\n/**\r\n * 텍스트 스타일 버튼 (굵게, 기울임, 밑줄, 취소선)\r\n */\r\nexport const TextStyleButton: React.FC<TextStyleButtonProps> = ({\r\n editor,\r\n style,\r\n}) => {\r\n // 현재 스타일 상태를 직접 계산\r\n const getIsActive = (): boolean => {\r\n try {\r\n const activeStyles = editor?.getActiveStyles?.() || {};\r\n return activeStyles[style] === true;\r\n } catch {\r\n return false;\r\n }\r\n };\r\n\r\n const isActive = getIsActive();\r\n\r\n const handleClick = useCallback(() => {\r\n try {\r\n editor?.toggleStyles?.({ [style]: true });\r\n } catch (err) {\r\n console.error(`Toggle ${style} failed:`, err);\r\n }\r\n }, [editor, style]);\r\n\r\n // 버튼 클릭 시 에디터 포커스/선택 영역 유지\r\n const handleMouseDown = useCallback((e: React.MouseEvent) => {\r\n e.preventDefault();\r\n }, []);\r\n\r\n return (\r\n <button\r\n className={cn(\"lumir-toolbar-btn\", isActive && \"is-active\")}\r\n onClick={handleClick}\r\n onMouseDown={handleMouseDown}\r\n title={titleMap[style]}\r\n type=\"button\"\r\n >\r\n {iconMap[style]}\r\n </button>\r\n );\r\n};\r\n","\"use client\";\r\n\r\nimport React, { useCallback } from \"react\";\r\nimport type { EditorType } from \"../../../types\";\r\nimport { Icons } from \"../Icons\";\r\nimport { cn } from \"../../../utils/cn\";\r\nimport {\r\n getFirstSelectedCellAttr,\r\n isInTableCell,\r\n setSelectedCellsAttr,\r\n} from \"../../../utils/prosemirror-table-utils\";\r\n\r\ntype Alignment = \"left\" | \"center\" | \"right\";\r\n\r\ninterface AlignButtonProps {\r\n editor: EditorType | any;\r\n alignment: Alignment;\r\n}\r\n\r\nconst iconMap: Record<Alignment, React.ReactNode> = {\r\n left: Icons.alignLeft,\r\n center: Icons.alignCenter,\r\n right: Icons.alignRight,\r\n};\r\n\r\nconst titleMap: Record<Alignment, string> = {\r\n left: \"왼쪽 정렬\",\r\n center: \"가운데 정렬\",\r\n right: \"오른쪽 정렬\",\r\n};\r\n\r\n/**\r\n * 텍스트 정렬 버튼 (왼쪽, 가운데, 오른쪽)\r\n */\r\nexport const AlignButton: React.FC<AlignButtonProps> = ({\r\n editor,\r\n alignment,\r\n}) => {\r\n // 현재 정렬 상태를 직접 계산 (표 셀이면 셀 attr, 아니면 블록 prop)\r\n const getCurrentAlignment = (): string => {\r\n try {\r\n if (isInTableCell(editor)) {\r\n return (\r\n (getFirstSelectedCellAttr(editor, \"textAlignment\") as string) ||\r\n \"left\"\r\n );\r\n }\r\n const block = editor?.getTextCursorPosition()?.block;\r\n return block?.props?.textAlignment || \"left\";\r\n } catch {\r\n return \"left\";\r\n }\r\n };\r\n\r\n const isActive = getCurrentAlignment() === alignment;\r\n\r\n const handleClick = useCallback(() => {\r\n try {\r\n // 표 셀 안이면 셀 단위 정렬(선택 유지), 아니면 블록 정렬.\r\n if (setSelectedCellsAttr(editor, \"textAlignment\", alignment)) {\r\n return;\r\n }\r\n const block = editor?.getTextCursorPosition()?.block;\r\n if (block && editor?.updateBlock) {\r\n editor.updateBlock(block, { props: { textAlignment: alignment } });\r\n }\r\n } catch (err) {\r\n console.error(`Set alignment ${alignment} failed:`, err);\r\n }\r\n }, [editor, alignment]);\r\n\r\n // 버튼 클릭 시 에디터 포커스/선택 영역 유지\r\n const handleMouseDown = useCallback((e: React.MouseEvent) => {\r\n e.preventDefault();\r\n }, []);\r\n\r\n return (\r\n <button\r\n className={cn(\"lumir-toolbar-btn\", isActive && \"is-active\")}\r\n onClick={handleClick}\r\n onMouseDown={handleMouseDown}\r\n title={titleMap[alignment]}\r\n type=\"button\"\r\n >\r\n {iconMap[alignment]}\r\n </button>\r\n );\r\n};\r\n","/**\r\n * ProseMirror selection에서 현재 선택된 테이블 셀의 위치(pos)를 반환한다.\r\n * CellSelection이면 선택된 모든 셀, 일반 selection이면 커서가 있는 셀.\r\n * prosemirror-tables 직접 임포트 없이 duck-typing으로 CellSelection을 감지한다.\r\n */\r\nexport function getSelectedCellPositions(editor: any): number[] {\r\n const tiptap = editor._tiptapEditor;\r\n if (!tiptap) return [];\r\n\r\n const { state } = tiptap;\r\n const { selection } = state;\r\n\r\n if (typeof selection.forEachCell === \"function\") {\r\n const positions: number[] = [];\r\n selection.forEachCell((_node: any, pos: number) => {\r\n positions.push(pos);\r\n });\r\n return positions;\r\n }\r\n\r\n const $pos = selection.$from;\r\n for (let depth = $pos.depth; depth > 0; depth--) {\r\n const node = $pos.node(depth);\r\n if (\r\n node.type.name === \"tableCell\" ||\r\n node.type.name === \"tableHeader\"\r\n ) {\r\n return [$pos.before(depth)];\r\n }\r\n }\r\n\r\n return [];\r\n}\r\n\r\n/**\r\n * 주어진 셀 위치들에 노드 attr(예: backgroundColor/textColor/textAlignment)를\r\n * setNodeMarkup으로 일괄 설정한다. setNodeMarkup은 문서 구조를 바꾸지 않으므로\r\n * **CellSelection이 유지**된다(updateBlock은 내용 전체를 교체해 선택이 풀림).\r\n *\r\n * backgroundColor/textColor는 BlockNote의 글로벌 attribute(tableCell/tableHeader),\r\n * textAlignment도 셀 노드 attr이므로 setNodeMarkup에 유효하다.\r\n *\r\n * @returns 적용된 셀이 하나라도 있으면 true(= 표 셀 컨텍스트), 아니면 false.\r\n */\r\nexport function setCellAttrAtPositions(\r\n editor: any,\r\n positions: number[],\r\n attr: string,\r\n value: unknown,\r\n): boolean {\r\n const tiptap = editor?._tiptapEditor;\r\n if (!tiptap || positions.length === 0) return false;\r\n\r\n let tr = tiptap.state.tr;\r\n let changed = false;\r\n for (const pos of positions) {\r\n const node = tr.doc.nodeAt(pos);\r\n if (\r\n node &&\r\n (node.type.name === \"tableCell\" || node.type.name === \"tableHeader\")\r\n ) {\r\n // setNodeMarkup은 attr만 바꾸고 위치/크기는 그대로 → 선택 매핑이 유지됨.\r\n tr = tr.setNodeMarkup(pos, undefined, { ...node.attrs, [attr]: value });\r\n changed = true;\r\n }\r\n }\r\n if (changed) {\r\n tiptap.view?.dispatch(tr);\r\n }\r\n return changed;\r\n}\r\n\r\n/**\r\n * 현재 selection의 셀(들)에 attr를 설정한다. 표 셀 컨텍스트가 아니면 false 반환\r\n * → 호출부가 블록/인라인 폴백을 적용할 수 있다.\r\n */\r\nexport function setSelectedCellsAttr(\r\n editor: any,\r\n attr: string,\r\n value: unknown,\r\n): boolean {\r\n return setCellAttrAtPositions(\r\n editor,\r\n getSelectedCellPositions(editor),\r\n attr,\r\n value,\r\n );\r\n}\r\n\r\n/** 현재 selection이 표 셀 안인지(단일/다중 무관). */\r\nexport function isInTableCell(editor: any): boolean {\r\n return getSelectedCellPositions(editor).length > 0;\r\n}\r\n\r\n/** 첫 선택 셀의 노드 attr 값을 읽는다(활성 상태 표시용). 없으면 undefined. */\r\nexport function getFirstSelectedCellAttr(\r\n editor: any,\r\n attr: string,\r\n): unknown {\r\n const tiptap = editor?._tiptapEditor;\r\n const positions = getSelectedCellPositions(editor);\r\n if (!tiptap || positions.length === 0) return undefined;\r\n const node = tiptap.state.doc.nodeAt(positions[0]);\r\n return node?.attrs?.[attr];\r\n}\r\n\r\n/**\r\n * blockId로 식별되는 table 블록의 ProseMirror 노드 pos를 찾는다.\r\n * blockContainer(id 일치) > firstChild(table)의 pos. 없으면 -1.\r\n */\r\nexport function findTableNodePos(tiptap: any, blockId: string): number {\r\n let tablePos = -1;\r\n tiptap.state.doc.descendants((node: any, pos: number) => {\r\n if (tablePos !== -1) return false;\r\n if (\r\n node.type.name === \"blockContainer\" &&\r\n node.attrs?.id === blockId &&\r\n node.firstChild?.type.name === \"table\"\r\n ) {\r\n // blockContainer content 시작(= table 노드 pos)\r\n tablePos = pos + 1;\r\n return false;\r\n }\r\n return undefined;\r\n });\r\n return tablePos;\r\n}\r\n\r\n/**\r\n * table 블록의 노드 attr `tableAlignment`를 설정한다(setNodeMarkup → 구조/선택 유지).\r\n * @returns 적용 성공 여부.\r\n */\r\nexport function setTableAlignment(\r\n editor: any,\r\n blockId: string,\r\n alignment: \"left\" | \"center\" | \"right\",\r\n): boolean {\r\n const tiptap = editor?._tiptapEditor;\r\n if (!tiptap || !blockId) return false;\r\n const tablePos = findTableNodePos(tiptap, blockId);\r\n if (tablePos < 0) return false;\r\n const node = tiptap.state.doc.nodeAt(tablePos);\r\n if (!node || node.type.name !== \"table\") return false;\r\n tiptap.view?.dispatch(\r\n tiptap.state.tr.setNodeMarkup(tablePos, undefined, {\r\n ...node.attrs,\r\n tableAlignment: alignment,\r\n }),\r\n );\r\n return true;\r\n}\r\n\r\n/** table 블록의 현재 정렬값을 읽는다(활성 상태 표시용). 기본 \"left\". */\r\nexport function getTableAlignment(editor: any, blockId: string): string {\r\n const tiptap = editor?._tiptapEditor;\r\n if (!tiptap || !blockId) return \"left\";\r\n const tablePos = findTableNodePos(tiptap, blockId);\r\n if (tablePos < 0) return \"left\";\r\n const node = tiptap.state.doc.nodeAt(tablePos);\r\n return (node?.attrs?.tableAlignment as string) || \"left\";\r\n}\r\n","\"use client\";\r\n\r\nimport React, { useCallback } from \"react\";\r\nimport type { EditorType } from \"../../../types\";\r\nimport { Icons } from \"../Icons\";\r\nimport { cn } from \"../../../utils/cn\";\r\n\r\ntype ListType = \"bullet\" | \"numbered\";\r\n\r\ninterface ListButtonProps {\r\n editor: EditorType | any;\r\n type: ListType;\r\n}\r\n\r\nconst iconMap: Record<ListType, React.ReactNode> = {\r\n bullet: Icons.bulletList,\r\n numbered: Icons.numberedList,\r\n};\r\n\r\nconst titleMap: Record<ListType, string> = {\r\n bullet: \"글머리 기호 목록\",\r\n numbered: \"번호 목록\",\r\n};\r\n\r\n/**\r\n * 리스트 버튼 (글머리 기호, 번호 목록)\r\n */\r\nexport const ListButton: React.FC<ListButtonProps> = ({ editor, type }) => {\r\n // 현재 리스트 상태를 직접 계산\r\n const getIsActive = (): boolean => {\r\n try {\r\n const block = editor?.getTextCursorPosition()?.block;\r\n const blockType =\r\n type === \"bullet\" ? \"bulletListItem\" : \"numberedListItem\";\r\n return block?.type === blockType;\r\n } catch {\r\n return false;\r\n }\r\n };\r\n\r\n const isActive = getIsActive();\r\n\r\n const handleClick = useCallback(() => {\r\n try {\r\n const block = editor?.getTextCursorPosition()?.block;\r\n if (block && editor?.updateBlock) {\r\n const targetType =\r\n type === \"bullet\" ? \"bulletListItem\" : \"numberedListItem\";\r\n const newType = block.type === targetType ? \"paragraph\" : targetType;\r\n editor.updateBlock(block, { type: newType as any });\r\n }\r\n } catch (err) {\r\n console.error(`List toggle failed:`, err);\r\n }\r\n }, [editor, type]);\r\n\r\n // 버튼 클릭 시 에디터 포커스/선택 영역 유지\r\n const handleMouseDown = useCallback((e: React.MouseEvent) => {\r\n e.preventDefault();\r\n }, []);\r\n\r\n return (\r\n <button\r\n className={cn(\"lumir-toolbar-btn\", isActive && \"is-active\")}\r\n onClick={handleClick}\r\n onMouseDown={handleMouseDown}\r\n title={titleMap[type]}\r\n type=\"button\"\r\n >\r\n {iconMap[type]}\r\n </button>\r\n );\r\n};\r\n","\"use client\";\r\n\r\nimport React, { useCallback } from \"react\";\r\nimport type { EditorType } from \"../../../types\";\r\nimport { Icons } from \"../Icons\";\r\n\r\ninterface ImageButtonProps {\r\n editor: EditorType | any;\r\n onImageUpload?: () => void;\r\n}\r\n\r\n/**\r\n * 이미지 업로드 버튼\r\n */\r\nexport const ImageButton: React.FC<ImageButtonProps> = ({\r\n editor,\r\n onImageUpload,\r\n}) => {\r\n const handleClick = useCallback(() => {\r\n if (onImageUpload) {\r\n onImageUpload();\r\n } else {\r\n const input = document.createElement(\"input\");\r\n input.type = \"file\";\r\n input.accept = \"image/*\";\r\n input.onchange = async (e) => {\r\n const file = (e.target as HTMLInputElement).files?.[0];\r\n if (file && editor?.uploadFile) {\r\n try {\r\n const url = await editor.uploadFile(file);\r\n editor.insertBlocks(\r\n [{ type: \"image\", props: { url: url as string } }] as any,\r\n editor.getTextCursorPosition().block,\r\n \"after\"\r\n );\r\n } catch (err) {\r\n console.error(\"Image upload failed:\", err);\r\n }\r\n }\r\n };\r\n input.click();\r\n }\r\n }, [editor, onImageUpload]);\r\n\r\n // 버튼 클릭 시 에디터 포커스/선택 영역 유지\r\n const handleMouseDown = useCallback((e: React.MouseEvent) => {\r\n e.preventDefault();\r\n }, []);\r\n\r\n return (\r\n <button\r\n className=\"lumir-toolbar-btn\"\r\n onClick={handleClick}\r\n onMouseDown={handleMouseDown}\r\n title=\"이미지 삽입\"\r\n type=\"button\"\r\n >\r\n {Icons.image}\r\n </button>\r\n );\r\n};\r\n","\"use client\";\r\n\r\nimport React, { useState, useEffect, useRef, useCallback } from \"react\";\r\nimport type { EditorType } from \"../../../types\";\r\nimport { Icons } from \"../Icons\";\r\nimport { cn } from \"../../../utils/cn\";\r\nimport {\r\n TEXT_COLORS,\r\n BACKGROUND_COLORS,\r\n getHexFromColorValue,\r\n} from \"../../../constants/colors\";\r\nimport {\r\n getFirstSelectedCellAttr,\r\n isInTableCell,\r\n setSelectedCellsAttr,\r\n} from \"../../../utils/prosemirror-table-utils\";\r\n\r\ntype ColorType = \"text\" | \"background\";\r\n\r\ninterface ColorButtonProps {\r\n editor: EditorType | any;\r\n type: ColorType;\r\n}\r\n\r\n/**\r\n * 색상 선택 버튼 (텍스트/배경 색상)\r\n */\r\nexport const ColorButton: React.FC<ColorButtonProps> = ({ editor, type }) => {\r\n const [isOpen, setIsOpen] = useState(false);\r\n const [currentColor, setCurrentColor] = useState(\"default\");\r\n const dropdownRef = useRef<HTMLDivElement>(null);\r\n\r\n const colors = type === \"text\" ? TEXT_COLORS : BACKGROUND_COLORS;\r\n\r\n const getCurrentColor = useCallback((): string => {\r\n try {\r\n // 표 셀 안이면 셀의 색 attr을 읽는다(인라인 활성 스타일이 아님).\r\n if (isInTableCell(editor)) {\r\n const attr = type === \"text\" ? \"textColor\" : \"backgroundColor\";\r\n return (getFirstSelectedCellAttr(editor, attr) as string) || \"default\";\r\n }\r\n const activeStyles = editor?.getActiveStyles?.() || {};\r\n if (type === \"text\" && activeStyles.textColor) {\r\n return activeStyles.textColor;\r\n } else if (type === \"background\" && activeStyles.backgroundColor) {\r\n return activeStyles.backgroundColor;\r\n }\r\n } catch {\r\n // ignore\r\n }\r\n return \"default\";\r\n }, [editor, type]);\r\n\r\n // 드롭다운 열릴 때 현재 색상 업데이트\r\n useEffect(() => {\r\n if (isOpen) {\r\n const color = getCurrentColor();\r\n setCurrentColor(color);\r\n }\r\n }, [isOpen, getCurrentColor]);\r\n\r\n // 외부 클릭 감지\r\n useEffect(() => {\r\n const handleClickOutside = (e: MouseEvent) => {\r\n if (\r\n dropdownRef.current &&\r\n !dropdownRef.current.contains(e.target as Node)\r\n ) {\r\n setIsOpen(false);\r\n }\r\n };\r\n document.addEventListener(\"mousedown\", handleClickOutside);\r\n return () => document.removeEventListener(\"mousedown\", handleClickOutside);\r\n }, []);\r\n\r\n const handleColorSelect = useCallback(\r\n (color: string) => {\r\n try {\r\n if (!editor) return;\r\n\r\n const attr = type === \"text\" ? \"textColor\" : \"backgroundColor\";\r\n // 표 셀 안이면 셀 단위로 적용(선택 유지), 아니면 인라인 스타일.\r\n if (!setSelectedCellsAttr(editor, attr, color)) {\r\n (editor as any).toggleStyles(\r\n type === \"text\"\r\n ? { textColor: color }\r\n : { backgroundColor: color },\r\n );\r\n }\r\n setCurrentColor(color);\r\n setIsOpen(false);\r\n setTimeout(() => editor.focus?.());\r\n } catch (err) {\r\n console.error(`Color apply failed:`, err);\r\n }\r\n },\r\n [editor, type]\r\n );\r\n\r\n // 버튼 클릭 시 에디터 포커스/선택 영역 유지\r\n const handleMouseDown = useCallback((e: React.MouseEvent) => {\r\n e.preventDefault();\r\n }, []);\r\n\r\n return (\r\n <div className=\"lumir-dropdown-wrapper\" ref={dropdownRef}>\r\n <button\r\n className=\"lumir-toolbar-btn lumir-color-btn\"\r\n onClick={() => setIsOpen(!isOpen)}\r\n onMouseDown={handleMouseDown}\r\n title={type === \"text\" ? \"텍스트 색상\" : \"배경 색상\"}\r\n type=\"button\"\r\n >\r\n {type === \"text\" ? Icons.textColor : Icons.bgColor}\r\n <span\r\n className=\"lumir-color-indicator\"\r\n style={{\r\n backgroundColor: getHexFromColorValue(currentColor, type),\r\n }}\r\n />\r\n </button>\r\n {isOpen && (\r\n <div className=\"lumir-dropdown-menu lumir-color-menu\">\r\n <div className=\"lumir-color-grid\">\r\n {colors.map((color) => (\r\n <button\r\n key={color.value}\r\n className={cn(\r\n \"lumir-color-swatch\",\r\n currentColor === color.value && \"is-active\"\r\n )}\r\n onClick={() => handleColorSelect(color.value)}\r\n onMouseDown={handleMouseDown}\r\n title={color.name}\r\n style={{ backgroundColor: color.hex }}\r\n type=\"button\"\r\n />\r\n ))}\r\n </div>\r\n </div>\r\n )}\r\n </div>\r\n );\r\n};\r\n","/**\r\n * 색상 팔레트 상수\r\n * BlockNote 기본 색상 팔레트와 일치\r\n */\r\n\r\nexport interface ColorItem {\r\n name: string;\r\n value: string;\r\n hex: string;\r\n}\r\n\r\n/**\r\n * 텍스트 색상 팔레트\r\n */\r\nexport const TEXT_COLORS: ColorItem[] = [\r\n { name: \"기본\", value: \"default\", hex: \"#3f3f3f\" },\r\n { name: \"회색\", value: \"gray\", hex: \"#9b9a97\" },\r\n { name: \"갈색\", value: \"brown\", hex: \"#64473a\" },\r\n { name: \"빨간색\", value: \"red\", hex: \"#e03e3e\" },\r\n { name: \"주황색\", value: \"orange\", hex: \"#d9730d\" },\r\n { name: \"노란색\", value: \"yellow\", hex: \"#dfab01\" },\r\n { name: \"초록색\", value: \"green\", hex: \"#4d6461\" },\r\n { name: \"파란색\", value: \"blue\", hex: \"#0b6e99\" },\r\n { name: \"보라색\", value: \"purple\", hex: \"#6940a5\" },\r\n { name: \"분홍색\", value: \"pink\", hex: \"#ad1a72\" },\r\n];\r\n\r\n/**\r\n * 배경 색상 팔레트\r\n */\r\nexport const BACKGROUND_COLORS: ColorItem[] = [\r\n { name: \"기본\", value: \"default\", hex: \"transparent\" },\r\n { name: \"회색\", value: \"gray\", hex: \"#ebeced\" },\r\n { name: \"갈색\", value: \"brown\", hex: \"#e9e5e3\" },\r\n { name: \"빨간색\", value: \"red\", hex: \"#fbe4e4\" },\r\n { name: \"주황색\", value: \"orange\", hex: \"#f6e9d9\" },\r\n { name: \"노란색\", value: \"yellow\", hex: \"#fbf3db\" },\r\n { name: \"초록색\", value: \"green\", hex: \"#ddedea\" },\r\n { name: \"파란색\", value: \"blue\", hex: \"#ddebf1\" },\r\n { name: \"보라색\", value: \"purple\", hex: \"#eae4f2\" },\r\n { name: \"분홍색\", value: \"pink\", hex: \"#f4dfeb\" },\r\n];\r\n\r\n/**\r\n * 색상 값으로 hex 색상 코드 찾기\r\n */\r\nexport const getHexFromColorValue = (\r\n value: string,\r\n type: \"text\" | \"background\"\r\n): string => {\r\n const colors = type === \"text\" ? TEXT_COLORS : BACKGROUND_COLORS;\r\n const colorItem = colors.find((c) => c.value === value);\r\n return colorItem?.hex || (type === \"text\" ? \"#000000\" : \"transparent\");\r\n};\r\n","\"use client\";\r\n\r\nimport React, { useState, useEffect, useRef, useCallback } from \"react\";\r\nimport type { EditorType } from \"../../../types\";\r\nimport { Icons } from \"../Icons\";\r\nimport { cn } from \"../../../utils/cn\";\r\nimport { FONT_SIZE_PRESETS } from \"../../../styles/FontSizeStyle\";\r\n\r\ninterface FontSizeButtonProps {\r\n editor: EditorType | any;\r\n}\r\n\r\nconst DEFAULT_LABEL = \"기본\";\r\n\r\n/** \"18px\" → \"18\" */\r\nconst toLabel = (size: string) => size.replace(/px$/, \"\");\r\n\r\n/**\r\n * 글자 크기 선택 드롭다운 (FloatingMenu용).\r\n *\r\n * ColorButton과 달리 테이블 셀 분기가 없다 — 셀 단위 font-size attr이 없으므로\r\n * 셀 내부에서도 항상 인라인 스타일(addStyles/removeStyles)로 적용한다.\r\n */\r\nexport const FontSizeButton: React.FC<FontSizeButtonProps> = ({ editor }) => {\r\n const [isOpen, setIsOpen] = useState(false);\r\n const dropdownRef = useRef<HTMLDivElement>(null);\r\n\r\n // 현재 인라인 글자 크기 (선택 변경 시 FloatingMenu가 tick으로 리렌더)\r\n const getCurrentSize = (): string => {\r\n try {\r\n return editor?.getActiveStyles?.()?.fontSize || \"\";\r\n } catch {\r\n return \"\";\r\n }\r\n };\r\n const currentSize = getCurrentSize();\r\n\r\n // 외부 클릭 감지\r\n useEffect(() => {\r\n const handleClickOutside = (e: MouseEvent) => {\r\n if (\r\n dropdownRef.current &&\r\n !dropdownRef.current.contains(e.target as Node)\r\n ) {\r\n setIsOpen(false);\r\n }\r\n };\r\n document.addEventListener(\"mousedown\", handleClickOutside);\r\n return () => document.removeEventListener(\"mousedown\", handleClickOutside);\r\n }, []);\r\n\r\n const handleSizeSelect = useCallback(\r\n (size: string) => {\r\n try {\r\n if (!editor) return;\r\n if (size === \"\") {\r\n (editor as any).removeStyles?.({ fontSize: \"\" });\r\n } else {\r\n (editor as any).addStyles?.({ fontSize: size });\r\n }\r\n setIsOpen(false);\r\n setTimeout(() => editor.focus?.());\r\n } catch (err) {\r\n console.error(\"Font size apply failed:\", err);\r\n }\r\n },\r\n [editor]\r\n );\r\n\r\n // 버튼 클릭 시 에디터 포커스/선택 영역 유지\r\n const handleMouseDown = useCallback((e: React.MouseEvent) => {\r\n e.preventDefault();\r\n }, []);\r\n\r\n return (\r\n <div className=\"lumir-dropdown-wrapper\" ref={dropdownRef}>\r\n <button\r\n className=\"lumir-dropdown-btn lumir-font-size-btn\"\r\n onClick={() => setIsOpen(!isOpen)}\r\n onMouseDown={handleMouseDown}\r\n title=\"글자 크기\"\r\n type=\"button\"\r\n >\r\n <span className=\"lumir-font-size-label\">\r\n {currentSize ? toLabel(currentSize) : DEFAULT_LABEL}\r\n </span>\r\n {Icons.expandMore}\r\n </button>\r\n {isOpen && (\r\n <div className=\"lumir-dropdown-menu lumir-font-size-menu\">\r\n <button\r\n className={cn(\r\n \"lumir-dropdown-item\",\r\n currentSize === \"\" && \"is-active\"\r\n )}\r\n onClick={() => handleSizeSelect(\"\")}\r\n onMouseDown={handleMouseDown}\r\n type=\"button\"\r\n >\r\n {DEFAULT_LABEL}\r\n </button>\r\n {FONT_SIZE_PRESETS.map((size) => (\r\n <button\r\n key={size}\r\n className={cn(\r\n \"lumir-dropdown-item\",\r\n currentSize === size && \"is-active\"\r\n )}\r\n onClick={() => handleSizeSelect(size)}\r\n onMouseDown={handleMouseDown}\r\n type=\"button\"\r\n >\r\n {toLabel(size)}\r\n </button>\r\n ))}\r\n </div>\r\n )}\r\n </div>\r\n );\r\n};\r\n","\"use client\";\r\n\r\nimport React, { useState, useEffect, useRef, useCallback } from \"react\";\r\nimport type { EditorType } from \"../../../types\";\r\nimport { Icons } from \"../Icons\";\r\n\r\ninterface LinkButtonProps {\r\n editor: EditorType | any;\r\n}\r\n\r\n/**\r\n * 🔒 위험한 URL 프로토콜 검증\r\n * javascript:, data:, vbscript: 등 XSS 공격에 사용될 수 있는 프로토콜 차단\r\n */\r\nexport const isDangerousProtocol = (url: string): boolean => {\r\n const trimmedUrl = url.trim().toLowerCase();\r\n // 위험한 프로토콜 패턴\r\n const dangerousPatterns = [\r\n /^javascript:/i,\r\n /^data:/i,\r\n /^vbscript:/i,\r\n /^file:/i,\r\n ];\r\n return dangerousPatterns.some((pattern) => pattern.test(trimmedUrl));\r\n};\r\n\r\n/**\r\n * URL 프로토콜 자동 추가 유틸리티 (보안 강화)\r\n */\r\nexport const normalizeUrl = (url: string): string | null => {\r\n const trimmedUrl = url.trim();\r\n\r\n // 🔒 위험한 프로토콜 차단\r\n if (isDangerousProtocol(trimmedUrl)) {\r\n console.warn(\"Blocked dangerous URL protocol:\", trimmedUrl);\r\n return null;\r\n }\r\n\r\n // 이미 프로토콜이 있는 경우 그대로 반환\r\n if (/^https?:\\/\\//i.test(trimmedUrl)) {\r\n return trimmedUrl;\r\n }\r\n\r\n // mailto: 또는 tel: 링크인 경우 그대로 반환\r\n if (/^(mailto:|tel:)/i.test(trimmedUrl)) {\r\n return trimmedUrl;\r\n }\r\n\r\n // 프로토콜이 없는 경우 https:// 추가\r\n return `https://${trimmedUrl}`;\r\n};\r\n\r\n/**\r\n * 링크 삽입 버튼\r\n */\r\nexport const LinkButton: React.FC<LinkButtonProps> = ({ editor }) => {\r\n const [isOpen, setIsOpen] = useState(false);\r\n const [linkUrl, setLinkUrl] = useState(\"\");\r\n const [errorMsg, setErrorMsg] = useState<string | null>(null);\r\n const dropdownRef = useRef<HTMLDivElement>(null);\r\n const inputRef = useRef<HTMLInputElement>(null);\r\n const hasSelectionRef = useRef(false);\r\n\r\n // 외부 클릭 감지\r\n useEffect(() => {\r\n const handleClickOutside = (e: MouseEvent) => {\r\n if (\r\n dropdownRef.current &&\r\n !dropdownRef.current.contains(e.target as Node)\r\n ) {\r\n setIsOpen(false);\r\n setLinkUrl(\"\");\r\n setErrorMsg(null);\r\n }\r\n };\r\n document.addEventListener(\"mousedown\", handleClickOutside);\r\n return () => document.removeEventListener(\"mousedown\", handleClickOutside);\r\n }, []);\r\n\r\n // 드롭다운 열릴 때 선택 상태 저장 후 input에 포커스\r\n useEffect(() => {\r\n if (isOpen && inputRef.current) {\r\n try {\r\n const selectedText = editor?.getSelectedText?.() || \"\";\r\n hasSelectionRef.current = selectedText.length > 0;\r\n } catch {\r\n hasSelectionRef.current = false;\r\n }\r\n setTimeout(() => inputRef.current?.focus(), 0);\r\n }\r\n }, [isOpen, editor]);\r\n\r\n const handleSubmit = useCallback(\r\n (e?: React.FormEvent) => {\r\n e?.preventDefault();\r\n setErrorMsg(null);\r\n\r\n try {\r\n if (linkUrl.trim() && editor?.createLink) {\r\n const normalizedUrl = normalizeUrl(linkUrl);\r\n\r\n if (normalizedUrl === null) {\r\n setErrorMsg(\"허용되지 않는 URL 형식입니다.\");\r\n return;\r\n }\r\n\r\n editor.focus();\r\n\r\n if (hasSelectionRef.current) {\r\n editor.createLink(normalizedUrl);\r\n } else {\r\n editor.createLink(normalizedUrl, normalizedUrl);\r\n }\r\n\r\n setIsOpen(false);\r\n setLinkUrl(\"\");\r\n }\r\n } catch (err) {\r\n console.error(\"Create link failed:\", err);\r\n setErrorMsg(\"링크 생성에 실패했습니다.\");\r\n }\r\n },\r\n [editor, linkUrl]\r\n );\r\n\r\n const handleCancel = useCallback(() => {\r\n setIsOpen(false);\r\n setLinkUrl(\"\");\r\n setErrorMsg(null);\r\n }, []);\r\n\r\n // 버튼 클릭 시 에디터 포커스/선택 영역 유지\r\n const handleMouseDown = useCallback((e: React.MouseEvent) => {\r\n e.preventDefault();\r\n }, []);\r\n\r\n const handleKeyDown = useCallback(\r\n (e: React.KeyboardEvent<HTMLInputElement>) => {\r\n if (e.key === \"Enter\") {\r\n handleSubmit();\r\n } else if (e.key === \"Escape\") {\r\n handleCancel();\r\n }\r\n },\r\n [handleSubmit, handleCancel]\r\n );\r\n\r\n return (\r\n <div className=\"lumir-dropdown-wrapper\" ref={dropdownRef}>\r\n <button\r\n className=\"lumir-toolbar-btn\"\r\n onClick={() => setIsOpen(!isOpen)}\r\n onMouseDown={handleMouseDown}\r\n title=\"링크 삽입\"\r\n type=\"button\"\r\n >\r\n {Icons.link}\r\n </button>\r\n {isOpen && (\r\n <div className=\"lumir-dropdown-menu lumir-link-menu\">\r\n <form onSubmit={handleSubmit} className=\"lumir-link-form\">\r\n <input\r\n ref={inputRef}\r\n type=\"text\"\r\n className=\"lumir-link-input\"\r\n placeholder=\"링크 URL을 입력하세요\"\r\n value={linkUrl}\r\n onChange={(e) => {\r\n setLinkUrl(e.target.value);\r\n setErrorMsg(null);\r\n }}\r\n onKeyDown={handleKeyDown}\r\n onMouseDown={handleMouseDown}\r\n />\r\n {/* 에러 메시지 표시 */}\r\n {errorMsg && (\r\n <div\r\n style={{\r\n color: \"#dc3545\",\r\n fontSize: \"12px\",\r\n marginTop: \"4px\",\r\n padding: \"0 4px\",\r\n }}\r\n >\r\n {errorMsg}\r\n </div>\r\n )}\r\n <div className=\"lumir-link-actions\">\r\n <button\r\n type=\"button\"\r\n className=\"lumir-link-btn lumir-link-cancel\"\r\n onClick={handleCancel}\r\n onMouseDown={handleMouseDown}\r\n >\r\n 취소\r\n </button>\r\n <button\r\n type=\"submit\"\r\n className=\"lumir-link-btn lumir-link-submit\"\r\n onMouseDown={handleMouseDown}\r\n disabled={!linkUrl.trim()}\r\n >\r\n 확인\r\n </button>\r\n </div>\r\n </form>\r\n </div>\r\n )}\r\n </div>\r\n );\r\n};\r\n","\"use client\";\r\n\r\nimport React, { useCallback } from \"react\";\r\nimport type { EditorType } from \"../../../types\";\r\nimport { Icons } from \"../Icons\";\r\n\r\ninterface TableButtonProps {\r\n editor: EditorType | any;\r\n}\r\n\r\n/**\r\n * 테이블 삽입 버튼\r\n */\r\nexport const TableButton: React.FC<TableButtonProps> = ({ editor }) => {\r\n const handleClick = useCallback(() => {\r\n try {\r\n const block = editor?.getTextCursorPosition()?.block;\r\n if (!block || !editor?.insertBlocks) return;\r\n\r\n // 3x3 기본 테이블 생성\r\n const defaultCell = [{ type: \"text\", text: \"\", styles: {} }];\r\n const tableContent = {\r\n type: \"tableContent\",\r\n rows: [\r\n { cells: [defaultCell, defaultCell, defaultCell] },\r\n { cells: [defaultCell, defaultCell, defaultCell] },\r\n { cells: [defaultCell, defaultCell, defaultCell] },\r\n ],\r\n };\r\n\r\n editor.insertBlocks(\r\n [{ type: \"table\", content: tableContent }] as any,\r\n block,\r\n \"after\"\r\n );\r\n } catch (err) {\r\n console.error(\"Table insert failed:\", err);\r\n }\r\n }, [editor]);\r\n\r\n // 버튼 클릭 시 에디터 포커스/선택 영역 유지\r\n const handleMouseDown = useCallback((e: React.MouseEvent) => {\r\n e.preventDefault();\r\n }, []);\r\n\r\n return (\r\n <button\r\n className=\"lumir-toolbar-btn\"\r\n onClick={handleClick}\r\n onMouseDown={handleMouseDown}\r\n title=\"테이블 삽입\"\r\n type=\"button\"\r\n >\r\n {Icons.table}\r\n </button>\r\n );\r\n};\r\n","\"use client\";\r\n\r\nimport React, { useCallback, useRef } from \"react\";\r\nimport type { EditorType } from \"../../../types\";\r\nimport { Icons } from \"../Icons\";\r\n\r\ninterface HTMLImportButtonProps {\r\n editor: EditorType | any;\r\n}\r\n\r\n/**\r\n * HTML 파일 Import 버튼\r\n */\r\nexport const HTMLImportButton: React.FC<HTMLImportButtonProps> = ({\r\n editor,\r\n}) => {\r\n const fileInputRef = useRef<HTMLInputElement>(null);\r\n\r\n const handleFileUpload = useCallback(\r\n (e: React.ChangeEvent<HTMLInputElement>) => {\r\n const file = e.target.files?.[0];\r\n if (!file) return;\r\n\r\n const reader = new FileReader();\r\n reader.onload = (event) => {\r\n const content = event.target?.result as string;\r\n\r\n try {\r\n if (!editor || !content.trim()) return;\r\n\r\n const block = editor?.getTextCursorPosition()?.block;\r\n if (!block || !editor?.insertBlocks) return;\r\n\r\n // htmlPreview 블록 삽입\r\n editor.insertBlocks(\r\n [\r\n {\r\n type: \"htmlPreview\",\r\n props: {\r\n htmlContent: content,\r\n fileName: file.name,\r\n height: \"400px\",\r\n },\r\n } as any,\r\n ],\r\n block,\r\n \"after\"\r\n );\r\n\r\n // file input 초기화\r\n if (fileInputRef.current) {\r\n fileInputRef.current.value = \"\";\r\n }\r\n } catch (err) {\r\n console.error(\"HTML insert failed:\", err);\r\n }\r\n };\r\n reader.readAsText(file);\r\n },\r\n [editor]\r\n );\r\n\r\n const handleClick = useCallback(() => {\r\n fileInputRef.current?.click();\r\n }, []);\r\n\r\n // 버튼 클릭 시 에디터 포커스/선택 영역 유지\r\n const handleMouseDown = useCallback((e: React.MouseEvent) => {\r\n e.preventDefault();\r\n }, []);\r\n\r\n return (\r\n <>\r\n <input\r\n ref={fileInputRef}\r\n type=\"file\"\r\n accept=\".html,.htm\"\r\n onChange={handleFileUpload}\r\n style={{ display: \"none\" }}\r\n />\r\n <button\r\n className=\"lumir-toolbar-btn\"\r\n onClick={handleClick}\r\n onMouseDown={handleMouseDown}\r\n title=\"HTML Import\"\r\n type=\"button\"\r\n >\r\n {Icons.htmlFile}\r\n </button>\r\n </>\r\n );\r\n};\r\n","\"use client\";\r\n\r\nimport React, { useState, useEffect, useRef, useCallback } from \"react\";\r\nimport type { EditorType } from \"../../../types\";\r\nimport { Icons, BlockTypeIcons } from \"../Icons\";\r\nimport { cn } from \"../../../utils/cn\";\r\n\r\ninterface BlockTypeSelectProps {\r\n editor: EditorType | any;\r\n}\r\n\r\n// 블록 타입 정의\r\ntype BlockTypeItem = {\r\n type: string;\r\n label: string;\r\n icon: string;\r\n level?: number;\r\n isToggle?: boolean;\r\n};\r\n\r\n// 카테고리별 블록 타입\r\nconst blockTypeCategories: { category: string; items: BlockTypeItem[] }[] = [\r\n {\r\n category: \"Headings\",\r\n items: [\r\n { type: \"heading\", label: \"Heading 1\", level: 1, icon: \"h1\", isToggle: false },\r\n { type: \"heading\", label: \"Heading 2\", level: 2, icon: \"h2\", isToggle: false },\r\n { type: \"heading\", label: \"Heading 3\", level: 3, icon: \"h3\", isToggle: false },\r\n { type: \"heading\", label: \"Toggle Heading 1\", level: 1, icon: \"toggleH1\", isToggle: true },\r\n { type: \"heading\", label: \"Toggle Heading 2\", level: 2, icon: \"toggleH2\", isToggle: true },\r\n { type: \"heading\", label: \"Toggle Heading 3\", level: 3, icon: \"toggleH3\", isToggle: true },\r\n ],\r\n },\r\n {\r\n category: \"Basic blocks\",\r\n items: [\r\n { type: \"paragraph\", label: \"Paragraph\", icon: \"paragraph\" },\r\n { type: \"quote\", label: \"Quote\", icon: \"quote\" },\r\n { type: \"codeBlock\", label: \"Code Block\", icon: \"codeBlock\" },\r\n { type: \"bulletListItem\", label: \"Bullet List\", icon: \"bulletList\" },\r\n { type: \"numberedListItem\", label: \"Numbered List\", icon: \"numberedList\" },\r\n { type: \"checkListItem\", label: \"Check List\", icon: \"checkList\" },\r\n { type: \"toggleListItem\", label: \"Toggle List\", icon: \"toggleList\" },\r\n ],\r\n },\r\n];\r\n\r\n// 평탄화된 블록 타입 목록\r\nconst blockTypes: BlockTypeItem[] = blockTypeCategories.flatMap(\r\n (cat) => cat.items\r\n);\r\n\r\n/**\r\n * 블록 타입 선택 드롭다운\r\n */\r\nexport const BlockTypeSelect: React.FC<BlockTypeSelectProps> = ({ editor }) => {\r\n const [isOpen, setIsOpen] = useState(false);\r\n const dropdownRef = useRef<HTMLDivElement>(null);\r\n\r\n // 현재 블록 타입을 직접 계산\r\n const getCurrentBlock = () => {\r\n try {\r\n return editor?.getTextCursorPosition()?.block;\r\n } catch {\r\n return null;\r\n }\r\n };\r\n\r\n const currentBlock = getCurrentBlock();\r\n const currentType = currentBlock?.type || \"paragraph\";\r\n const currentLevel = currentBlock?.props?.level;\r\n const isCurrentToggle =\r\n currentType === \"heading\" && currentBlock?.props?.isToggleable === true;\r\n\r\n useEffect(() => {\r\n const handleClickOutside = (e: MouseEvent) => {\r\n if (\r\n dropdownRef.current &&\r\n !dropdownRef.current.contains(e.target as Node)\r\n ) {\r\n setIsOpen(false);\r\n }\r\n };\r\n document.addEventListener(\"mousedown\", handleClickOutside);\r\n return () => document.removeEventListener(\"mousedown\", handleClickOutside);\r\n }, []);\r\n\r\n const handleTypeChange = (\r\n type: string,\r\n level?: number,\r\n isToggle?: boolean\r\n ) => {\r\n try {\r\n const block = editor?.getTextCursorPosition()?.block;\r\n if (!block || !editor) return;\r\n\r\n const props: any = {};\r\n if (level) props.level = level;\r\n\r\n if (type === \"heading\" && isToggle !== undefined) {\r\n props.isToggleable = isToggle;\r\n editor.updateBlock(block, {\r\n type: \"heading\" as any,\r\n props,\r\n });\r\n } else {\r\n editor.updateBlock(block, { type: type as any, props });\r\n }\r\n\r\n setIsOpen(false);\r\n } catch (err) {\r\n console.error(\"Block type change failed:\", err);\r\n }\r\n };\r\n\r\n const handleMouseDown = useCallback((e: React.MouseEvent) => {\r\n e.preventDefault();\r\n }, []);\r\n\r\n const getCurrentLabel = () => {\r\n if (currentType === \"heading\" && currentLevel) {\r\n const found = blockTypes.find(\r\n (bt) =>\r\n bt.type === \"heading\" &&\r\n bt.level === currentLevel &&\r\n bt.isToggle === isCurrentToggle\r\n );\r\n return found?.label || \"Heading\";\r\n }\r\n const found = blockTypes.find((bt) => bt.type === currentType);\r\n return found?.label || \"Paragraph\";\r\n };\r\n\r\n const getCurrentIcon = () => {\r\n if (currentType === \"heading\" && currentLevel) {\r\n const found = blockTypes.find(\r\n (bt) =>\r\n bt.type === \"heading\" &&\r\n bt.level === currentLevel &&\r\n bt.isToggle === isCurrentToggle\r\n );\r\n return found?.icon || `h${currentLevel}`;\r\n }\r\n const found = blockTypes.find((bt) => bt.type === currentType);\r\n return found?.icon || \"paragraph\";\r\n };\r\n\r\n const isActiveItem = (bt: BlockTypeItem) => {\r\n if (bt.type === \"heading\" && bt.level) {\r\n const isLevelMatch =\r\n currentType === \"heading\" && currentLevel === bt.level;\r\n const isToggleMatch = bt.isToggle === isCurrentToggle;\r\n return isLevelMatch && isToggleMatch;\r\n }\r\n return currentType === bt.type;\r\n };\r\n\r\n return (\r\n <div className=\"lumir-dropdown-wrapper\" ref={dropdownRef}>\r\n <button\r\n className=\"lumir-dropdown-btn lumir-block-type-btn\"\r\n onClick={() => setIsOpen(!isOpen)}\r\n onMouseDown={handleMouseDown}\r\n type=\"button\"\r\n >\r\n <span className=\"lumir-block-icon\">\r\n {BlockTypeIcons[getCurrentIcon()]}\r\n </span>\r\n <span className=\"lumir-block-label\">{getCurrentLabel()}</span>\r\n {Icons.expandMore}\r\n </button>\r\n {isOpen && (\r\n <div className=\"lumir-dropdown-menu lumir-block-menu\">\r\n {blockTypeCategories.map((category) => (\r\n <div key={category.category} className=\"lumir-block-category\">\r\n <div className=\"lumir-block-category-title\">\r\n {category.category}\r\n </div>\r\n {category.items.map((bt) => (\r\n <button\r\n key={bt.icon}\r\n className={cn(\r\n \"lumir-dropdown-item lumir-block-item\",\r\n isActiveItem(bt) && \"is-active\"\r\n )}\r\n onClick={() => handleTypeChange(bt.type, bt.level, bt.isToggle)}\r\n onMouseDown={handleMouseDown}\r\n >\r\n <span className=\"lumir-block-icon\">\r\n {BlockTypeIcons[bt.icon]}\r\n </span>\r\n <span className=\"lumir-block-item-title\">{bt.label}</span>\r\n </button>\r\n ))}\r\n </div>\r\n ))}\r\n </div>\r\n )}\r\n </div>\r\n );\r\n};\r\n","/**\r\n * LumirEditor 커스텀 에러 클래스\r\n */\r\n\r\nexport type LumirErrorCode =\r\n | \"UPLOAD_FAILED\"\r\n | \"INVALID_FILE_TYPE\"\r\n | \"S3_CONFIG_ERROR\"\r\n | \"PRESIGNED_URL_ERROR\"\r\n | \"NETWORK_ERROR\"\r\n | \"EDITOR_ERROR\"\r\n | \"UNKNOWN_ERROR\";\r\n\r\nexport interface LumirErrorDetails {\r\n code: LumirErrorCode;\r\n originalError?: Error;\r\n context?: Record<string, unknown>;\r\n}\r\n\r\n/**\r\n * LumirEditor에서 발생하는 에러를 위한 커스텀 에러 클래스\r\n */\r\nexport class LumirEditorError extends Error {\r\n public readonly code: LumirErrorCode;\r\n public readonly originalError?: Error;\r\n public readonly context?: Record<string, unknown>;\r\n\r\n constructor(message: string, details: Partial<LumirErrorDetails> = {}) {\r\n super(message);\r\n this.name = \"LumirEditorError\";\r\n this.code = details.code || \"UNKNOWN_ERROR\";\r\n this.originalError = details.originalError;\r\n this.context = details.context;\r\n\r\n // Error 클래스 확장 시 프로토타입 체인 유지\r\n Object.setPrototypeOf(this, LumirEditorError.prototype);\r\n }\r\n\r\n /**\r\n * 에러 정보를 JSON 형태로 반환\r\n */\r\n toJSON(): Record<string, unknown> {\r\n return {\r\n name: this.name,\r\n message: this.message,\r\n code: this.code,\r\n context: this.context,\r\n stack: this.stack,\r\n };\r\n }\r\n\r\n /**\r\n * 사용자 친화적 에러 메시지 반환\r\n */\r\n getUserMessage(): string {\r\n switch (this.code) {\r\n case \"UPLOAD_FAILED\":\r\n return \"파일 업로드에 실패했습니다. 다시 시도해주세요.\";\r\n case \"INVALID_FILE_TYPE\":\r\n return \"지원하지 않는 파일 형식입니다. 이미지 파일만 업로드 가능합니다.\";\r\n case \"S3_CONFIG_ERROR\":\r\n return \"S3 설정이 올바르지 않습니다. 관리자에게 문의하세요.\";\r\n case \"PRESIGNED_URL_ERROR\":\r\n return \"업로드 URL 생성에 실패했습니다. 다시 시도해주세요.\";\r\n case \"NETWORK_ERROR\":\r\n return \"네트워크 연결을 확인해주세요.\";\r\n case \"EDITOR_ERROR\":\r\n return \"에디터 오류가 발생했습니다. 페이지를 새로고침해주세요.\";\r\n default:\r\n return \"알 수 없는 오류가 발생했습니다.\";\r\n }\r\n }\r\n\r\n /**\r\n * 일반 Error를 LumirEditorError로 변환\r\n */\r\n static fromError(\r\n error: Error,\r\n code: LumirErrorCode = \"UNKNOWN_ERROR\",\r\n context?: Record<string, unknown>\r\n ): LumirEditorError {\r\n return new LumirEditorError(error.message, {\r\n code,\r\n originalError: error,\r\n context,\r\n });\r\n }\r\n\r\n /**\r\n * 업로드 실패 에러 생성\r\n */\r\n static uploadFailed(\r\n message: string,\r\n originalError?: Error\r\n ): LumirEditorError {\r\n return new LumirEditorError(message, {\r\n code: \"UPLOAD_FAILED\",\r\n originalError,\r\n });\r\n }\r\n\r\n /**\r\n * 잘못된 파일 형식 에러 생성\r\n * @param allowVideoUpload true이면 \"image and video\" 메시지 사용\r\n */\r\n static invalidFileType(\r\n fileName: string,\r\n allowVideoUpload?: boolean\r\n ): LumirEditorError {\r\n const message =\r\n allowVideoUpload === true\r\n ? `Invalid file type: ${fileName}. Only image and video files are allowed.`\r\n : `Invalid file type: ${fileName}. Only image files are allowed.`;\r\n return new LumirEditorError(message, {\r\n code: \"INVALID_FILE_TYPE\",\r\n context: { fileName },\r\n });\r\n }\r\n\r\n /**\r\n * S3 설정 에러 생성\r\n */\r\n static s3ConfigError(message: string): LumirEditorError {\r\n return new LumirEditorError(message, {\r\n code: \"S3_CONFIG_ERROR\",\r\n });\r\n }\r\n\r\n /**\r\n * 네트워크 에러 생성\r\n */\r\n static networkError(originalError?: Error): LumirEditorError {\r\n return new LumirEditorError(\"Network request failed\", {\r\n code: \"NETWORK_ERROR\",\r\n originalError,\r\n });\r\n }\r\n}\r\n","import { Extension } from \"@tiptap/core\";\r\n\r\nexport type VerticalAlignment = \"top\" | \"middle\" | \"bottom\";\r\n\r\nexport const VerticalAlignmentExtension = Extension.create({\r\n name: \"verticalAlignment\",\r\n\r\n addGlobalAttributes() {\r\n return [\r\n {\r\n types: [\"tableCell\", \"tableHeader\"],\r\n attributes: {\r\n verticalAlignment: {\r\n default: \"top\",\r\n parseHTML: (element) => {\r\n return (\r\n element.getAttribute(\"data-vertical-alignment\") || \"top\"\r\n );\r\n },\r\n renderHTML: (attributes) => {\r\n if (\r\n !attributes.verticalAlignment ||\r\n attributes.verticalAlignment === \"top\"\r\n ) {\r\n return {};\r\n }\r\n return {\r\n \"data-vertical-alignment\": attributes.verticalAlignment,\r\n };\r\n },\r\n },\r\n },\r\n },\r\n ];\r\n },\r\n\r\n addProseMirrorPlugins() {\r\n return [];\r\n },\r\n});\r\n","import { Extension } from \"@tiptap/core\";\r\nimport { rowResizing } from \"./rowResizing\";\r\nimport { tableScaling } from \"./tableScaling\";\r\nimport { tableCellAttrPreserve } from \"./tableCellAttrPreserve\";\r\n\r\nexport interface RowHeightOptions {\r\n /**\r\n * 행 경계 드래그 리사이즈(rowResizing 플러그인) 활성화 여부.\r\n * false면 속성 등록/렌더링은 유지하되 드래그 UI만 비활성화한다\r\n * (= 저장된 행 높이는 그대로 표시되지만 사용자가 조절할 수 없음).\r\n * LumirEditor에서 `tableHandles` prop으로 게이트한다.\r\n */\r\n resizable: boolean;\r\n}\r\n\r\n/**\r\n * 표 셀에 `rowHeight`(px) 노드 attr을 추가하는 Tiptap 확장.\r\n *\r\n * BlockNote 0.35는 행 높이를 모델링하지 않으므로(`TableContent`에 `columnWidths`만 존재),\r\n * `verticalAlignment`와 동일한 패턴으로 셀 단위 글로벌 attribute를 등록한다:\r\n * - renderHTML이 inline `style=\"height:Npx\"`를 출력 → HTML 복사/내보내기/Excel 붙여넣기에 라운드트립\r\n * - parseHTML이 `style.height` 또는 `data-row-height`를 역파싱\r\n * - 저장(블록 JSON 주입)은 utils/table-cell-props.ts의 injectTableCellAttrs가 담당\r\n * - 로드는 BlockNote blockToNode가 cell.props를 노드 attr로 펼쳐 자동 복원\r\n *\r\n * 드래그 리사이즈 UI는 addProseMirrorPlugins에서 rowResizing 플러그인으로 제공하며,\r\n * resizable 옵션으로 게이트한다.\r\n */\r\nexport const RowHeightExtension = Extension.create<RowHeightOptions>({\r\n name: \"rowHeight\",\r\n\r\n addOptions() {\r\n return { resizable: true };\r\n },\r\n\r\n addGlobalAttributes() {\r\n return [\r\n {\r\n types: [\"tableCell\", \"tableHeader\"],\r\n attributes: {\r\n rowHeight: {\r\n default: null,\r\n parseHTML: (element) => {\r\n const fromStyle = parseInt(element.style?.height ?? \"\", 10);\r\n if (Number.isFinite(fromStyle) && fromStyle > 0) {\r\n return fromStyle;\r\n }\r\n const fromAttr = parseInt(\r\n element.getAttribute(\"data-row-height\") ?? \"\",\r\n 10,\r\n );\r\n return Number.isFinite(fromAttr) && fromAttr > 0\r\n ? fromAttr\r\n : null;\r\n },\r\n renderHTML: (attributes) => {\r\n const h = attributes.rowHeight;\r\n if (!h || typeof h !== \"number\") {\r\n return {};\r\n }\r\n return { style: `height: ${h}px` };\r\n },\r\n },\r\n },\r\n },\r\n ];\r\n },\r\n\r\n addProseMirrorPlugins() {\r\n // 커스텀 셀 attr 보존은 항상(드래그 비활성이어도 행/열 추가 등으로 유실 방지).\r\n // 드래그 리사이즈 UI만 resizable로 게이트.\r\n const plugins = [tableCellAttrPreserve()];\r\n if (this.options.resizable) {\r\n plugins.push(rowResizing());\r\n // 표 전체 종횡비 고정 스케일(우하단 코너 드래그).\r\n plugins.push(tableScaling());\r\n }\r\n return plugins;\r\n },\r\n});\r\n","/**\r\n * 표 **행 높이(row height)** 드래그 리사이즈 ProseMirror 플러그인.\r\n *\r\n * prosemirror-tables의 `columnResizing`(열 너비)을 **수직축으로 미러링**한 구현이다.\r\n * columnResizing이 없는 행 버전을 동일한 구조로 직접 작성한다:\r\n *\r\n * - 아무 행의 **아래 경계**(또는 위 경계)에 hover → `row-resize` 커서 + 핸들 노출\r\n * - 경계를 드래그 → 대상 행 높이를 **실시간 프리뷰**(셀에 height 데코레이션)\r\n * - 드롭 → 해당 행의 모든 셀에 `rowHeight` 노드 attr을 `setNodeMarkup`으로 일괄 커밋\r\n *\r\n * 저장은 셀 단위 `rowHeight` 속성(RowHeightExtension의 글로벌 attribute)으로 이뤄지며,\r\n * 이는 BlockNote가 행 props를 모델링하지 않기 때문이다(행 높이 = 행 셀들의 max height).\r\n *\r\n * 라이브 프리뷰 방식(중요):\r\n * 열 리사이즈는 `<colgroup>`을 직접 조작해 프리뷰한다(BlockNoteTableView.ignoreMutation이\r\n * colgroup 변경을 무시하므로 PM이 되돌리지 않음). 행에는 colgroup 등가물이 없고, 셀/행의\r\n * style을 직접 바꾸면 PM의 MutationObserver가 노드를 즉시 다시 그려 초기화한다.\r\n * → 그래서 드래그 중 높이를 **Decoration.node({style:'height:Npx'})**로 입힌다(PM이 직접\r\n * 렌더하므로 mutation 충돌 없음). 높이 갱신은 **meta-only 트랜잭션**으로 하여 doc을 바꾸지\r\n * 않으므로 onContentChange/undo 스팸이 없다. 드롭 시 한 번만 실제 커밋(단일 undo 스텝).\r\n *\r\n * ⚠️ prosemirror-state/view/tables는 반드시 에디터(@blocknote)와 **동일한 인스턴스**를\r\n * 써야 한다(Decoration/Plugin instanceof). 따라서 raw `prosemirror-*`에서 import하고\r\n * tsup 빌드에서 external 처리한다(@blocknote가 같은 패키지를 런타임 의존성으로 쓰므로\r\n * 소비자 트리에서 단일 복사본으로 dedup된다).\r\n */\r\n\r\nimport { Plugin, PluginKey } from \"prosemirror-state\";\r\nimport type { EditorState } from \"prosemirror-state\";\r\nimport { Decoration, DecorationSet } from \"prosemirror-view\";\r\nimport type { EditorView } from \"prosemirror-view\";\r\nimport { TableMap, cellAround, pointsAtCell } from \"prosemirror-tables\";\r\nimport {\r\n ROW_RESIZE_HANDLE_WIDTH,\r\n ROW_RESIZE_MIN_HEIGHT,\r\n} from \"../constants/limits\";\r\n\r\nexport const rowResizingPluginKey = new PluginKey<RowResizeState>(\r\n \"lumirRowResizing\",\r\n);\r\n\r\ntype Dragging = {\r\n startY: number;\r\n startHeight: number;\r\n /** 현재 드래그 위치 기준 프리뷰 높이(px). 데코레이션이 이 값을 렌더한다. */\r\n currentHeight: number;\r\n};\r\n\r\n/**\r\n * 플러그인 상태. activeHandle = 리사이즈 대상 셀의 pos(없으면 -1),\r\n * dragging = 드래그 중이면 시작 좌표/높이/현재 프리뷰 높이, 아니면 null.\r\n */\r\nexport class RowResizeState {\r\n constructor(\r\n public activeHandle: number,\r\n public dragging: Dragging | null,\r\n ) {}\r\n\r\n apply(tr: any): RowResizeState {\r\n const action = tr.getMeta(rowResizingPluginKey);\r\n if (action && action.setHandle != null) {\r\n return new RowResizeState(action.setHandle, null);\r\n }\r\n if (action && action.setDragging !== undefined) {\r\n return new RowResizeState(this.activeHandle, action.setDragging);\r\n }\r\n if (this.activeHandle > -1 && tr.docChanged) {\r\n let handle = tr.mapping.map(this.activeHandle, -1);\r\n if (!pointsAtCell(tr.doc.resolve(handle))) {\r\n handle = -1;\r\n }\r\n return new RowResizeState(handle, this.dragging);\r\n }\r\n return this;\r\n }\r\n}\r\n\r\nexport function rowResizing({\r\n handleWidth = ROW_RESIZE_HANDLE_WIDTH,\r\n minHeight = ROW_RESIZE_MIN_HEIGHT,\r\n}: { handleWidth?: number; minHeight?: number } = {}): Plugin {\r\n return new Plugin<RowResizeState>({\r\n key: rowResizingPluginKey,\r\n state: {\r\n init() {\r\n return new RowResizeState(-1, null);\r\n },\r\n apply(tr, prev) {\r\n return prev.apply(tr);\r\n },\r\n },\r\n props: {\r\n attributes: (state): Record<string, string> => {\r\n const pluginState = rowResizingPluginKey.getState(state);\r\n return pluginState && pluginState.activeHandle > -1\r\n ? { class: \"row-resize-cursor\" }\r\n : {};\r\n },\r\n handleDOMEvents: {\r\n mousemove: (view, event) => {\r\n handleMouseMove(view, event as MouseEvent, handleWidth);\r\n },\r\n mouseleave: (view) => {\r\n handleMouseLeave(view);\r\n },\r\n mousedown: (view, event) => {\r\n handleMouseDown(view, event as MouseEvent, minHeight);\r\n },\r\n },\r\n decorations: (state) => {\r\n const pluginState = rowResizingPluginKey.getState(state);\r\n if (pluginState && pluginState.activeHandle > -1) {\r\n return handleDecorations(state, pluginState);\r\n }\r\n return undefined;\r\n },\r\n },\r\n });\r\n}\r\n\r\nfunction handleMouseMove(\r\n view: EditorView,\r\n event: MouseEvent,\r\n handleWidth: number,\r\n) {\r\n if (!view.editable) {\r\n return;\r\n }\r\n const pluginState = rowResizingPluginKey.getState(view.state);\r\n if (!pluginState) {\r\n return;\r\n }\r\n if (!pluginState.dragging) {\r\n const target = domCellAround(event.target as Node | null);\r\n let cell = -1;\r\n if (target) {\r\n const { top, bottom } = (target as HTMLElement).getBoundingClientRect();\r\n if (event.clientY - top <= handleWidth) {\r\n cell = edgeCell(view, event, \"top\", handleWidth);\r\n } else if (bottom - event.clientY <= handleWidth) {\r\n cell = edgeCell(view, event, \"bottom\", handleWidth);\r\n }\r\n }\r\n if (cell !== pluginState.activeHandle) {\r\n updateHandle(view, cell);\r\n }\r\n }\r\n}\r\n\r\nfunction handleMouseLeave(view: EditorView) {\r\n if (!view.editable) {\r\n return;\r\n }\r\n const pluginState = rowResizingPluginKey.getState(view.state);\r\n if (pluginState && pluginState.activeHandle > -1 && !pluginState.dragging) {\r\n updateHandle(view, -1);\r\n }\r\n}\r\n\r\nfunction handleMouseDown(\r\n view: EditorView,\r\n event: MouseEvent,\r\n minHeight: number,\r\n): boolean {\r\n if (!view.editable) {\r\n return false;\r\n }\r\n const win = view.dom.ownerDocument.defaultView ?? window;\r\n const pluginState = rowResizingPluginKey.getState(view.state);\r\n if (!pluginState || pluginState.activeHandle === -1 || pluginState.dragging) {\r\n return false;\r\n }\r\n\r\n const startHeight = currentRowHeight(view, pluginState.activeHandle);\r\n setDragging(view, {\r\n startY: event.clientY,\r\n startHeight,\r\n currentHeight: startHeight,\r\n });\r\n\r\n function finish(finishEvent: MouseEvent) {\r\n win.removeEventListener(\"mouseup\", finish);\r\n win.removeEventListener(\"mousemove\", move);\r\n const ps = rowResizingPluginKey.getState(view.state);\r\n if (ps?.dragging) {\r\n const finalHeight = draggedHeight(ps.dragging, finishEvent, minHeight);\r\n // ⚠️ 순서 중요: 프리뷰 데코(셀 style:height)를 먼저 제거한 뒤 커밋한다.\r\n // 데코의 inline style과 커밋 후 renderHTML의 style이 같은 style 속성을 두고\r\n // 충돌하는데, 커밋 후 데코를 제거하면 PM이 style을 비우고 renderHTML 값을\r\n // 복원하지 않는다(빈 style). 데코를 먼저 없애면 커밋 시 renderHTML이 깨끗이\r\n // 적용된다. 두 dispatch는 동기로 연달아 일어나 중간 페인트가 없어 깜빡임도 없다.\r\n setDragging(view, null);\r\n commitRowHeight(view, ps.activeHandle, finalHeight);\r\n }\r\n }\r\n\r\n function move(moveEvent: MouseEvent) {\r\n // 버튼이 떼어진 채 들어온 mousemove면(놓친 mouseup 안전장치) 즉시 마무리.\r\n // 표준 `buttons`(0=눌림 없음)를 우선 보고, 레거시 `which`를 폴백으로 본다.\r\n if (\r\n moveEvent.buttons === 0 ||\r\n (moveEvent.buttons === undefined && !moveEvent.which)\r\n ) {\r\n return finish(moveEvent);\r\n }\r\n const ps = rowResizingPluginKey.getState(view.state);\r\n if (!ps?.dragging) {\r\n return;\r\n }\r\n const h = draggedHeight(ps.dragging, moveEvent, minHeight);\r\n if (h !== ps.dragging.currentHeight) {\r\n // doc은 바꾸지 않고 프리뷰 높이만 갱신(meta-only) → 데코레이션이 즉시 반영.\r\n setDragging(view, { ...ps.dragging, currentHeight: h });\r\n }\r\n }\r\n\r\n win.addEventListener(\"mouseup\", finish);\r\n win.addEventListener(\"mousemove\", move);\r\n event.preventDefault();\r\n return true;\r\n}\r\n\r\n/** 드래그 상태를 갱신하는 meta-only 트랜잭션(doc 미변경). */\r\nfunction setDragging(view: EditorView, dragging: Dragging | null) {\r\n view.dispatch(\r\n view.state.tr.setMeta(rowResizingPluginKey, { setDragging: dragging }),\r\n );\r\n}\r\n\r\n/** 렌더된 `<tr>` 높이를 드래그 시작 높이로 쓴다(미설정 행도 자연스러운 시작점). */\r\nfunction currentRowHeight(view: EditorView, cellPos: number): number {\r\n const info = targetRowInfo(view, cellPos);\r\n const tr = rowTrElement(view, cellPos, info.row);\r\n return tr ? tr.offsetHeight : ROW_RESIZE_MIN_HEIGHT;\r\n}\r\n\r\n/** 드롭 시 커밋: 대상 행에서 **끝나는** 모든 셀에 rowHeight attr 일괄 적용(기록 O). */\r\nfunction commitRowHeight(view: EditorView, cellPos: number, height: number) {\r\n const { table, map, start, row } = targetRowInfo(view, cellPos);\r\n const tr = view.state.tr;\r\n const seen = new Set<number>();\r\n for (let col = 0; col < map.width; col++) {\r\n const cellRelPos = map.map[row * map.width + col];\r\n if (seen.has(cellRelPos)) {\r\n continue; // colspan: 같은 셀 중복 스킵\r\n }\r\n seen.add(cellRelPos);\r\n const rect = map.findCell(cellRelPos);\r\n // rowspan 셀이 이 행에서 끝나지 않으면(아래로 더 뻗으면) origin 행 높이를 유지\r\n if (rect.bottom - 1 !== row) {\r\n continue;\r\n }\r\n const node = table.nodeAt(cellRelPos);\r\n if (!node || node.attrs.rowHeight === height) {\r\n continue;\r\n }\r\n tr.setNodeMarkup(start + cellRelPos, undefined, {\r\n ...node.attrs,\r\n rowHeight: height,\r\n });\r\n }\r\n if (tr.docChanged) {\r\n view.dispatch(tr);\r\n }\r\n}\r\n\r\n/** activeHandle 셀이 속한 테이블/맵/대상 행 인덱스(이 셀의 아래 경계가 위치한 행)를 구한다. */\r\nfunction targetRowInfo(view: EditorView, cellPos: number) {\r\n const $cell = view.state.doc.resolve(cellPos);\r\n const table = $cell.node(-1);\r\n const map = TableMap.get(table);\r\n const start = $cell.start(-1);\r\n const rect = map.findCell($cell.pos - start);\r\n return { table, map, start, row: rect.bottom - 1, $cell };\r\n}\r\n\r\n/** activeHandle 셀이 속한 `<table>`을 찾아 그 안의 row번째 `<tr>`을 반환. */\r\nfunction rowTrElement(\r\n view: EditorView,\r\n cellPos: number,\r\n row: number,\r\n): HTMLTableRowElement | null {\r\n let dom: Node | null = view.nodeDOM(cellPos) as Node | null;\r\n while (dom && dom.nodeName !== \"TABLE\") {\r\n dom = dom.parentNode;\r\n }\r\n if (!dom) {\r\n return null;\r\n }\r\n return (dom as HTMLTableElement).rows[row] ?? null;\r\n}\r\n\r\nfunction domCellAround(target: Node | null): HTMLElement | null {\r\n let node: any = target;\r\n while (node && node.nodeName !== \"TD\" && node.nodeName !== \"TH\") {\r\n node = node.classList?.contains(\"ProseMirror\") ? null : node.parentNode;\r\n }\r\n return node as HTMLElement | null;\r\n}\r\n\r\n/**\r\n * 경계에 인접한 셀 pos를 구한다.\r\n * - \"bottom\": 커서가 닿은 셀(이 셀의 아래 경계를 드래그)\r\n * - \"top\": 윗 행의 셀(그 셀의 아래 경계 = 이 경계). map index - width.\r\n */\r\nfunction edgeCell(\r\n view: EditorView,\r\n event: MouseEvent,\r\n side: \"top\" | \"bottom\",\r\n handleWidth: number,\r\n): number {\r\n const offset = side === \"bottom\" ? -handleWidth : handleWidth;\r\n const found = view.posAtCoords({\r\n left: event.clientX,\r\n top: event.clientY + offset,\r\n });\r\n if (!found) {\r\n return -1;\r\n }\r\n const $cell = cellAround(view.state.doc.resolve(found.pos));\r\n if (!$cell) {\r\n return -1;\r\n }\r\n if (side === \"bottom\") {\r\n return $cell.pos;\r\n }\r\n const map = TableMap.get($cell.node(-1));\r\n const start = $cell.start(-1);\r\n const index = map.map.indexOf($cell.pos - start);\r\n return index < map.width ? -1 : start + map.map[index - map.width];\r\n}\r\n\r\nfunction updateHandle(view: EditorView, value: number) {\r\n view.dispatch(\r\n view.state.tr.setMeta(rowResizingPluginKey, { setHandle: value }),\r\n );\r\n}\r\n\r\nfunction draggedHeight(\r\n dragging: Dragging,\r\n event: MouseEvent,\r\n minHeight: number,\r\n): number {\r\n const offset = event.clientY - dragging.startY;\r\n return Math.max(minHeight, dragging.startHeight + offset);\r\n}\r\n\r\n/**\r\n * activeHandle이 가리키는 행의 각 셀에 데코레이션을 그린다.\r\n * - 하단 경계 핸들 위젯(.row-resize-handle)\r\n * - 드래그 중이면: 셀에 height 인라인 style 데코레이션(라이브 프리뷰) + dragging 클래스\r\n */\r\nfunction handleDecorations(\r\n state: EditorState,\r\n pluginState: RowResizeState,\r\n): DecorationSet {\r\n const decorations: Decoration[] = [];\r\n const $cell = state.doc.resolve(pluginState.activeHandle);\r\n const table = $cell.node(-1);\r\n if (!table) {\r\n return DecorationSet.empty;\r\n }\r\n const map = TableMap.get(table);\r\n const start = $cell.start(-1);\r\n const row = map.findCell($cell.pos - start).bottom - 1;\r\n const dragging = pluginState.dragging;\r\n const seen = new Set<number>();\r\n\r\n for (let col = 0; col < map.width; col++) {\r\n const cellRelPos = map.map[row * map.width + col];\r\n if (seen.has(cellRelPos)) {\r\n continue;\r\n }\r\n seen.add(cellRelPos);\r\n if (map.findCell(cellRelPos).bottom - 1 !== row) {\r\n continue;\r\n }\r\n const node = table.nodeAt(cellRelPos);\r\n if (!node) {\r\n continue;\r\n }\r\n const from = start + cellRelPos;\r\n const to = from + node.nodeSize;\r\n\r\n if (dragging) {\r\n // 라이브 프리뷰: 셀에 height를 입혀 마우스를 따라 즉시 커진다(PM이 직접 렌더).\r\n decorations.push(\r\n Decoration.node(from, to, {\r\n class: \"row-resize-dragging\",\r\n style: `height: ${dragging.currentHeight}px`,\r\n }),\r\n );\r\n }\r\n\r\n const handle = document.createElement(\"div\");\r\n handle.className = \"row-resize-handle\";\r\n decorations.push(Decoration.widget(to - 1, handle));\r\n }\r\n\r\n return DecorationSet.create(state.doc, decorations);\r\n}\r\n","/**\r\n * 보안 및 성능 제한 상수\r\n */\r\n\r\n/** 최대 파일 크기: 10MB (이미지) */\r\nexport const MAX_FILE_SIZE = 10 * 1024 * 1024;\r\n\r\n/** 최대 동영상 파일 크기: 100MB */\r\nexport const MAX_VIDEO_FILE_SIZE = 100 * 1024 * 1024;\r\n\r\n/** 업로드 타임아웃: 30초 (이미지 등) */\r\nexport const UPLOAD_TIMEOUT = 30000;\r\n\r\n/** 대용량 파일(비디오 등) 업로드 타임아웃: 120초 */\r\nexport const UPLOAD_TIMEOUT_VIDEO = 120000;\r\n\r\n/** 허용된 이미지 MIME 타입 (SVG 제외) */\r\nexport const ALLOWED_IMAGE_MIME_TYPES = new Set([\r\n \"image/jpeg\",\r\n \"image/png\",\r\n \"image/gif\",\r\n \"image/webp\",\r\n \"image/bmp\",\r\n]);\r\n\r\n/** 차단된 파일 확장자 */\r\nexport const BLOCKED_EXTENSIONS = [\".svg\", \".svgz\"];\r\n\r\n/** 허용된 이미지 확장자 */\r\nexport const ALLOWED_IMAGE_EXTENSIONS = [\r\n \".png\",\r\n \".jpg\",\r\n \".jpeg\",\r\n \".gif\",\r\n \".webp\",\r\n \".bmp\",\r\n];\r\n\r\n/** 허용된 동영상 MIME 타입 */\r\nexport const ALLOWED_VIDEO_MIME_TYPES = new Set([\r\n \"video/mp4\",\r\n \"video/webm\",\r\n \"video/ogg\",\r\n \"video/quicktime\", // .mov\r\n]);\r\n\r\n/** 허용된 동영상 확장자 */\r\nexport const ALLOWED_VIDEO_EXTENSIONS = [\r\n \".mp4\",\r\n \".webm\",\r\n \".ogg\",\r\n \".mov\",\r\n];\r\n\r\n/** 표 행 높이 리사이즈: 최소 행 높이(px) */\r\nexport const ROW_RESIZE_MIN_HEIGHT = 24;\r\n\r\n/** 표 행 높이 리사이즈: 행 경계 hit-area 두께(px). columnResizing handleWidth(5)와 동일. */\r\nexport const ROW_RESIZE_HANDLE_WIDTH = 5;\r\n\r\n/** 표 전체 스케일(코너 드래그): 우하단 코너 hit-area 한 변(px). */\r\nexport const TABLE_SCALE_CORNER_SIZE = 16;\r\n\r\n/** 표 전체 스케일: 최소 열 너비(px). prosemirror-tables cellMinWidth(25) 근사. */\r\nexport const TABLE_SCALE_MIN_COL_WIDTH = 24;\r\n\r\n/** 표 전체 스케일: 최대 배율(런어웨이 방지). */\r\nexport const TABLE_SCALE_MAX = 6;\r\n","/**\r\n * 표 **전체 스케일**(우하단 코너 드래그)의 프리뷰/커밋 엔진 ProseMirror 플러그인.\r\n *\r\n * UI 핸들과 드래그 추적은 LumirTableHandlesController(에디터 DOM 밖 오버레이)가 담당한다.\r\n * 코너가 rowResizing(하단 경계)·columnResizing(우측 경계) 핸들과 겹쳐 mousedown이 그쪽으로\r\n * 새는 것을 피하기 위해, 핸들은 에디터 DOM 밖에 두고 자체 포인터 이벤트로 처리한다.\r\n * 이 플러그인은 컨트롤러가 dispatch한 프리뷰 상태를 받아:\r\n * - 높이: 셀 `Decoration.node({style:height})` 라이브 프리뷰\r\n * - 너비: 표 `<colgroup>` `<col>` width 직접 조작(BlockNoteTableView.ignoreMutation이 무시)\r\n * 로 그리고, 커밋 시 모든 셀의 colwidth/rowHeight를 일괄 setNodeMarkup(단일 undo)한다.\r\n * 갱신은 meta-only 트랜잭션이라 undo/onContentChange 스팸이 없다.\r\n *\r\n * ⚠️ prosemirror-* 는 @blocknote와 동일 인스턴스를 써야 한다(rowResizing와 동일 이유).\r\n */\r\n\r\nimport { Plugin, PluginKey } from \"prosemirror-state\";\r\nimport type { EditorState } from \"prosemirror-state\";\r\nimport { Decoration, DecorationSet } from \"prosemirror-view\";\r\nimport type { EditorView } from \"prosemirror-view\";\r\nimport { TableMap } from \"prosemirror-tables\";\r\n\r\nexport const tableScalingPluginKey = new PluginKey<TableScalePreview | null>(\r\n \"lumirTableScaling\",\r\n);\r\n\r\nexport type TableScalePreview = {\r\n /** 대상 table 노드 pos. */\r\n tablePos: number;\r\n /** 드래그 시작 시점의 열 너비/행 높이(px) — materialize 값. */\r\n colWidths: number[];\r\n rowHeights: number[];\r\n /** 드래그 시작 시점의 표 렌더 W×H(px). */\r\n origW: number;\r\n origH: number;\r\n /** 현재 배율(≥1 또는 <1). */\r\n scale: number;\r\n};\r\n\r\nexport function tableScaling(): Plugin<TableScalePreview | null> {\r\n return new Plugin<TableScalePreview | null>({\r\n key: tableScalingPluginKey,\r\n state: {\r\n init: () => null,\r\n apply(tr, prev) {\r\n const meta = tr.getMeta(tableScalingPluginKey);\r\n if (meta !== undefined) return meta.preview as TableScalePreview | null;\r\n if (prev && tr.docChanged) {\r\n return { ...prev, tablePos: tr.mapping.map(prev.tablePos, -1) };\r\n }\r\n return prev;\r\n },\r\n },\r\n props: {\r\n decorations(state) {\r\n const p = tableScalingPluginKey.getState(state);\r\n return p ? buildHeightDecorations(state, p) : null;\r\n },\r\n },\r\n view: (view) => ({\r\n update: () => {\r\n const p = tableScalingPluginKey.getState(view.state);\r\n if (p) applyColgroupPreview(view, p);\r\n },\r\n }),\r\n });\r\n}\r\n\r\n/** 컨트롤러가 호출: 프리뷰 상태 설정/해제(meta-only). */\r\nexport function setTableScalePreview(\r\n view: EditorView,\r\n preview: TableScalePreview | null,\r\n): void {\r\n view.dispatch(view.state.tr.setMeta(tableScalingPluginKey, { preview }));\r\n}\r\n\r\n/** 컨트롤러가 호출: 드래그 시작 시 대상 표의 현재 열너비/행높이/렌더크기를 측정. */\r\nexport function measureTableForScale(\r\n view: EditorView,\r\n tablePos: number,\r\n): TableScalePreview | null {\r\n const tableEl = findTableEl(view.nodeDOM(tablePos));\r\n const node = view.state.doc.nodeAt(tablePos);\r\n if (!tableEl || !node || node.type.name !== \"table\") return null;\r\n const map = TableMap.get(node);\r\n const rect = tableEl.getBoundingClientRect();\r\n const body = tableEl.tBodies[0];\r\n const rowHeights = body\r\n ? Array.from(body.rows).map((tr) => tr.getBoundingClientRect().height)\r\n : [];\r\n const colWidths = measureColWidths(tableEl, map.width);\r\n if (rowHeights.length !== map.height || colWidths.length !== map.width) {\r\n return null;\r\n }\r\n return {\r\n tablePos,\r\n colWidths,\r\n rowHeights,\r\n origW: rect.width,\r\n origH: rect.height,\r\n scale: 1,\r\n };\r\n}\r\n\r\n/** 컨트롤러가 호출: 드롭 시 모든 셀의 colwidth/rowHeight를 ×scale로 일괄 커밋(단일 undo). */\r\nexport function commitTableScale(\r\n view: EditorView,\r\n preview: TableScalePreview,\r\n): void {\r\n const { state } = view;\r\n const { tablePos, colWidths, rowHeights, scale } = preview;\r\n const table = state.doc.nodeAt(tablePos);\r\n if (!table || table.type.name !== \"table\") return;\r\n const map = TableMap.get(table);\r\n const start = tablePos + 1;\r\n const tr = state.tr;\r\n const seen = new Set<number>();\r\n\r\n for (const relPos of map.map) {\r\n if (seen.has(relPos)) continue;\r\n seen.add(relPos);\r\n const node = table.nodeAt(relPos);\r\n if (!node) continue;\r\n const rect = map.findCell(relPos);\r\n const colwidth: number[] = [];\r\n for (let c = rect.left; c < rect.right; c++) {\r\n colwidth.push(Math.round((colWidths[c] ?? 0) * scale));\r\n }\r\n let h = 0;\r\n for (let r = rect.top; r < rect.bottom; r++) h += rowHeights[r] ?? 0;\r\n const rowHeight = Math.round(h * scale);\r\n tr.setNodeMarkup(start + relPos, undefined, {\r\n ...node.attrs,\r\n colwidth: colwidth.some((w) => w > 0) ? colwidth : null,\r\n rowHeight: rowHeight > 0 ? rowHeight : null,\r\n });\r\n }\r\n if (tr.docChanged) view.dispatch(tr);\r\n}\r\n\r\n// ── 측정 보조 ─────────────────────────────────────────────────\r\nfunction measureColWidths(tableEl: HTMLTableElement, width: number): number[] {\r\n const widths = new Array(width).fill(0);\r\n const colgroup = tableEl.querySelector(\"colgroup\");\r\n if (colgroup && colgroup.children.length === width) {\r\n let allSet = true;\r\n for (let i = 0; i < width; i++) {\r\n const w = parseFloat((colgroup.children[i] as HTMLElement).style.width);\r\n if (Number.isFinite(w) && w > 0) widths[i] = w;\r\n else allSet = false;\r\n }\r\n if (allSet) return widths;\r\n }\r\n const firstRow = tableEl.tBodies[0]?.rows[0];\r\n if (firstRow) {\r\n let col = 0;\r\n for (const cell of Array.from(firstRow.cells)) {\r\n const span = cell.colSpan || 1;\r\n const w = cell.getBoundingClientRect().width / span;\r\n for (let s = 0; s < span && col < width; s++) widths[col++] = w;\r\n }\r\n }\r\n return widths;\r\n}\r\n\r\n// ── 프리뷰 렌더 ────────────────────────────────────────────────\r\nfunction applyColgroupPreview(view: EditorView, p: TableScalePreview) {\r\n const tableEl = findTableEl(view.nodeDOM(p.tablePos));\r\n const colgroup = tableEl?.querySelector(\"colgroup\");\r\n if (!colgroup) return;\r\n const cols = colgroup.children;\r\n for (let i = 0; i < cols.length && i < p.colWidths.length; i++) {\r\n (cols[i] as HTMLElement).style.width =\r\n Math.round(p.colWidths[i] * p.scale) + \"px\";\r\n }\r\n}\r\n\r\nfunction buildHeightDecorations(\r\n state: EditorState,\r\n p: TableScalePreview,\r\n): DecorationSet {\r\n const table = state.doc.nodeAt(p.tablePos);\r\n if (!table || table.type.name !== \"table\") return DecorationSet.empty;\r\n const map = TableMap.get(table);\r\n const start = p.tablePos + 1;\r\n const decorations: Decoration[] = [];\r\n const seen = new Set<number>();\r\n for (const relPos of map.map) {\r\n if (seen.has(relPos)) continue;\r\n seen.add(relPos);\r\n const node = table.nodeAt(relPos);\r\n if (!node) continue;\r\n const rect = map.findCell(relPos);\r\n let h = 0;\r\n for (let r = rect.top; r < rect.bottom; r++) h += p.rowHeights[r] ?? 0;\r\n const from = start + relPos;\r\n const to = from + node.nodeSize;\r\n decorations.push(\r\n Decoration.node(from, to, {\r\n class: \"lumir-table-scale-dragging\",\r\n style: `height: ${Math.round(h * p.scale)}px`,\r\n }),\r\n );\r\n }\r\n return DecorationSet.create(state.doc, decorations);\r\n}\r\n\r\n/** 임의 DOM에서 가장 가까운 <table>을 찾는다(nodeDOM이 wrapper일 수 있음). */\r\nfunction findTableEl(dom: Node | null): HTMLTableElement | null {\r\n if (!dom) return null;\r\n const el = dom as HTMLElement;\r\n if (el.nodeName === \"TABLE\") return el as HTMLTableElement;\r\n const inner = el.querySelector?.(\"table\");\r\n if (inner) return inner as HTMLTableElement;\r\n return (el.closest?.(\"table\") as HTMLTableElement) ?? null;\r\n}\r\n","/**\r\n * 표 구조 편집 시 커스텀 셀 attr(rowHeight, verticalAlignment) 유실을 막는 플러그인.\r\n *\r\n * 문제:\r\n * BlockNote의 행/열 추가·삭제(ExtendButton)와 행/열 드래그 재정렬은 내부적으로\r\n * `editor.updateBlock(block, ...)`로 표 블록 전체를 재작성한다. 이때 사용하는 block은\r\n * `nodeToBlock`(contentNodeToTableContent)에서 온 것인데, 이 변환은 고정된 prop 집합\r\n * (colspan/rowspan/backgroundColor/textColor/textAlignment)만 cell.props에 담는다.\r\n * → rowHeight·verticalAlignment 같은 커스텀 글로벌 attribute는 그 시점에 누락되고,\r\n * updateBlock→blockToNode 재작성으로 PM 노드에서도 사라진다(셀 기본값으로 초기화).\r\n *\r\n * 해결:\r\n * appendTransaction에서 \"직전(old) 상태의 셀 attr\"과 \"현재(new) 상태\"를 비교해, 구조\r\n * 편집으로 기본값이 된 셀에 직전 값을 다시 채운다. (row, 행 내 cell) 인덱스로 매칭한다.\r\n *\r\n * - 사용자가 직접 리사이즈/정렬한 경우: 새 값이 non-default라 복원이 끼어들지 않는다.\r\n * - 새로 추가된 행/열의 셀: 직전 값이 없으므로 그대로 기본값(정상).\r\n * - self-terminating: 복원 후 재실행 시 new 값이 채워져 더 복원할 게 없다.\r\n */\r\n\r\nimport { Plugin, PluginKey } from \"prosemirror-state\";\r\nimport type { Node as PMNode } from \"prosemirror-model\";\r\n\r\nexport const tableCellAttrPreserveKey = new PluginKey(\r\n \"lumirTableCellAttrPreserve\",\r\n);\r\n\r\n/** 보존 대상 attr과 \"기본값\"(이 값이면 의미 없음 = 보존/복원 제외). */\r\nconst PRESERVED_ATTRS: { name: string; default: unknown }[] = [\r\n { name: \"rowHeight\", default: null },\r\n { name: \"verticalAlignment\", default: \"top\" },\r\n];\r\n\r\nfunction isMeaningful(attrName: string, value: unknown, def: unknown): boolean {\r\n if (value === null || value === undefined) {\r\n return false;\r\n }\r\n return value !== def;\r\n}\r\n\r\n/** 셀의 보존 대상 attr 중 의미 있는 값만 추린다. 없으면 null. */\r\nfunction meaningfulAttrs(node: PMNode): Record<string, unknown> | null {\r\n let out: Record<string, unknown> | null = null;\r\n for (const { name, default: def } of PRESERVED_ATTRS) {\r\n const v = node.attrs?.[name];\r\n if (isMeaningful(name, v, def)) {\r\n (out ??= {})[name] = v;\r\n }\r\n }\r\n return out;\r\n}\r\n\r\n/**\r\n * 문서에서 table 블록 id → \"row:cell\" → {attr:value} 맵을 만든다.\r\n * id는 blockContainer의 id attr(편집을 가로질러 유지됨)로 매칭한다.\r\n */\r\nfunction collectCellAttrs(\r\n doc: PMNode,\r\n): Map<string, Map<string, Record<string, unknown>>> {\r\n const result = new Map<string, Map<string, Record<string, unknown>>>();\r\n\r\n doc.descendants((node) => {\r\n if (node.type.name !== \"blockContainer\") {\r\n return undefined;\r\n }\r\n const id = node.attrs?.id as string | undefined;\r\n const table = node.firstChild;\r\n if (!id || table?.type.name !== \"table\") {\r\n // 표가 아닌 블록도 children에 표를 가질 수 있으므로 계속 descend.\r\n return undefined;\r\n }\r\n const cells = new Map<string, Record<string, unknown>>();\r\n let rowIndex = 0;\r\n table.forEach((rowNode) => {\r\n if (rowNode.type.name === \"tableRow\") {\r\n let cellIndex = 0;\r\n rowNode.forEach((cellNode) => {\r\n const attrs = meaningfulAttrs(cellNode);\r\n if (attrs) {\r\n cells.set(`${rowIndex}:${cellIndex}`, attrs);\r\n }\r\n cellIndex++;\r\n });\r\n rowIndex++;\r\n }\r\n });\r\n if (cells.size > 0) {\r\n result.set(id, cells);\r\n }\r\n // 표 셀 내부엔 더 깊은 표가 없으므로 하위 순회는 불필요.\r\n return false;\r\n });\r\n\r\n return result;\r\n}\r\n\r\nexport function tableCellAttrPreserve(): Plugin {\r\n return new Plugin({\r\n key: tableCellAttrPreserveKey,\r\n appendTransaction(transactions, oldState, newState) {\r\n if (!transactions.some((tr) => tr.docChanged)) {\r\n return null;\r\n }\r\n const oldMap = collectCellAttrs(oldState.doc);\r\n if (oldMap.size === 0) {\r\n return null;\r\n }\r\n\r\n const tr = newState.tr;\r\n let changed = false;\r\n\r\n // new 문서를 순회하며 구조 편집으로 기본값이 된 셀을 old 값으로 복원.\r\n let curTableCells: Map<string, Record<string, unknown>> | null = null;\r\n let curRowIndex = -1;\r\n let curCellIndex = 0;\r\n\r\n newState.doc.descendants((node, pos) => {\r\n const name = node.type.name;\r\n if (name === \"blockContainer\") {\r\n const id = node.attrs?.id as string | undefined;\r\n const table = node.firstChild;\r\n if (id && table?.type.name === \"table\" && oldMap.has(id)) {\r\n curTableCells = oldMap.get(id)!;\r\n curRowIndex = -1;\r\n } else {\r\n curTableCells = null;\r\n }\r\n return undefined; // 항상 descend(중첩 표 대응)\r\n }\r\n if (!curTableCells) {\r\n return undefined;\r\n }\r\n if (name === \"tableRow\") {\r\n curRowIndex++;\r\n curCellIndex = 0;\r\n return undefined;\r\n }\r\n if (name === \"tableCell\" || name === \"tableHeader\") {\r\n const wanted = curTableCells.get(`${curRowIndex}:${curCellIndex}`);\r\n curCellIndex++;\r\n if (wanted) {\r\n const patch: Record<string, unknown> = {};\r\n let needs = false;\r\n for (const { name: attrName, default: def } of PRESERVED_ATTRS) {\r\n if (!(attrName in wanted)) {\r\n continue;\r\n }\r\n const cur = node.attrs?.[attrName];\r\n // 현재 값이 기본값/빈값일 때만 복원(사용자가 새로 지정한 값은 보존).\r\n if (cur === null || cur === undefined || cur === def) {\r\n patch[attrName] = wanted[attrName];\r\n needs = true;\r\n }\r\n }\r\n if (needs) {\r\n tr.setNodeMarkup(pos, undefined, { ...node.attrs, ...patch });\r\n changed = true;\r\n }\r\n }\r\n return false; // 셀 내부는 순회 불필요\r\n }\r\n return undefined;\r\n });\r\n\r\n return changed ? tr : null;\r\n },\r\n });\r\n}\r\n","import { Extension } from \"@tiptap/core\";\r\nimport { Plugin, PluginKey } from \"prosemirror-state\";\r\nimport { Decoration, DecorationSet } from \"prosemirror-view\";\r\n\r\nexport type TableAlignment = \"left\" | \"center\" | \"right\";\r\n\r\nconst tableAlignmentDecoKey = new PluginKey(\"lumirTableAlignmentDeco\");\r\n\r\n/**\r\n * table 노드의 tableAlignment attr을 읽어 `data-table-alignment`를 노드 DOM에 입히는 데코레이션.\r\n *\r\n * 왜 renderHTML(글로벌 attr)만으로 부족한가: table 노드는 커스텀 nodeView\r\n * (BlockNoteTableView)를 사용하는데, 이 nodeView는 생성 시점의 HTMLAttributes만 wrapper div에\r\n * 적용하고 attr 변경 시 갱신하지 않는다. 그래서 setNodeMarkup으로 attr을 바꿔도 DOM에 반영되지\r\n * 않는다. Decoration.node는 PM이 매 state 변경마다 nodeView의 outer DOM에 속성을 적용하므로\r\n * 정렬 변경이 즉시 화면에 반영된다. (renderHTML attr은 HTML 내보내기/복사용으로 계속 유지.)\r\n */\r\nfunction tableAlignmentDecorationPlugin(): Plugin {\r\n return new Plugin({\r\n key: tableAlignmentDecoKey,\r\n props: {\r\n decorations(state) {\r\n const decorations: Decoration[] = [];\r\n state.doc.descendants((node, pos) => {\r\n if (node.type.name === \"table\") {\r\n const align = node.attrs.tableAlignment;\r\n if (align && align !== \"left\") {\r\n decorations.push(\r\n Decoration.node(pos, pos + node.nodeSize, {\r\n \"data-table-alignment\": align,\r\n }),\r\n );\r\n }\r\n return false; // 표 내부는 순회 불필요\r\n }\r\n return undefined;\r\n });\r\n return DecorationSet.create(state.doc, decorations);\r\n },\r\n },\r\n });\r\n}\r\n\r\n/**\r\n * 표(table) 블록 전체를 에디터 영역 기준으로 좌/가운데/우 정렬하는 Tiptap 확장.\r\n *\r\n * BlockNote 0.35는 테이블 블록 레벨 정렬을 모델링하지 않으므로, verticalAlignment/\r\n * rowHeight와 동일한 패턴으로 `table` 노드에 글로벌 attribute `tableAlignment`를 추가한다.\r\n * - renderHTML이 `data-table-alignment`를 출력(BlockNoteTableView가 HTMLAttributes를\r\n * 적용하는 .bn-block-content[data-content-type=table] div에 붙음) → CSS가 content-폭\r\n * <table>에 margin을 적용해 정렬한다(좌측은 기본이라 attr 미출력).\r\n * - parseHTML로 복사/붙여넣기 라운드트립.\r\n * - 저장(블록 JSON 주입)은 utils/table-vertical-alignment.ts의 injectTableBlockAttrs가 담당.\r\n * - 로드는 BlockNote blockToNode가 block.props를 table 노드 attr로 펼쳐 자동 복원.\r\n */\r\nexport const TableAlignmentExtension = Extension.create({\r\n name: \"tableAlignment\",\r\n\r\n addGlobalAttributes() {\r\n return [\r\n {\r\n types: [\"table\"],\r\n attributes: {\r\n tableAlignment: {\r\n default: \"left\",\r\n parseHTML: (element) =>\r\n element.getAttribute(\"data-table-alignment\") || \"left\",\r\n renderHTML: (attributes) => {\r\n if (\r\n !attributes.tableAlignment ||\r\n attributes.tableAlignment === \"left\"\r\n ) {\r\n return {};\r\n }\r\n return { \"data-table-alignment\": attributes.tableAlignment };\r\n },\r\n },\r\n },\r\n },\r\n ];\r\n },\r\n\r\n addProseMirrorPlugins() {\r\n return [tableAlignmentDecorationPlugin()];\r\n },\r\n});\r\n","import { Extension } from \"@tiptap/core\";\r\nimport { CellSelection, TableMap } from \"prosemirror-tables\";\r\n\r\n/**\r\n * 표 셀에 포커스(커서/선택)가 있을 때 **Ctrl/Cmd+A로 표 전체(모든 셀)를 선택**하는 확장.\r\n *\r\n * 기본 Mod-a는 문서 전체를 선택하지만, 표 안에서는 먼저 표 전체를 선택하는 편이\r\n * 사용성이 좋다(셀 일괄 색/정렬/삭제 등). 동작:\r\n * - 셀 안인데 표 전체가 아직 선택되지 않음 → 표 전체 CellSelection (handled)\r\n * - 이미 표 전체가 선택됨 → false 반환 → 기본 동작(문서 전체 선택)으로 단계적 확장\r\n * - 표 밖 → false (기본 문서 전체 선택)\r\n *\r\n * priority를 높여 기본 selectAll보다 먼저 처리한다.\r\n */\r\nexport const TableSelectAllExtension = Extension.create({\r\n name: \"lumirTableSelectAll\",\r\n priority: 1000,\r\n\r\n addKeyboardShortcuts() {\r\n return {\r\n \"Mod-a\": () => {\r\n const view = this.editor.view;\r\n const { state } = view;\r\n const sel: any = state.selection;\r\n const $from = sel.$from;\r\n\r\n // 선택을 감싸는 table 노드의 depth를 찾는다.\r\n let depth = -1;\r\n for (let d = $from.depth; d > 0; d--) {\r\n if ($from.node(d).type.name === \"table\") {\r\n depth = d;\r\n break;\r\n }\r\n }\r\n if (depth < 0) return false; // 표 밖 → 기본 동작\r\n\r\n const table = $from.node(depth);\r\n const tableStart = $from.start(depth);\r\n const map = TableMap.get(table);\r\n const firstRel = map.map[0]; // 좌상단 셀\r\n const lastRel = map.map[map.map.length - 1]; // 우하단 셀\r\n\r\n // 이미 표 전체가 선택돼 있으면 기본 동작(문서 전체)로 넘긴다.\r\n if (sel instanceof CellSelection) {\r\n const a = sel.$anchorCell.pos - tableStart;\r\n const h = sel.$headCell.pos - tableStart;\r\n if (Math.min(a, h) === firstRel && Math.max(a, h) === lastRel) {\r\n return false;\r\n }\r\n }\r\n\r\n const tr = state.tr.setSelection(\r\n CellSelection.create(\r\n state.doc,\r\n tableStart + firstRel,\r\n tableStart + lastRel,\r\n ),\r\n );\r\n view.dispatch(tr);\r\n return true;\r\n },\r\n };\r\n },\r\n});\r\n","import { TextSelection } from \"prosemirror-state\";\r\n\r\n/**\r\n * 현재 블록 뒤에 2단 컬럼(columnList > column×2 > 빈 단락)을 삽입한다.\r\n *\r\n * BlockNote의 editor.insertBlocks는 public blockSchema에 없는 타입(columnList/column)을\r\n * \"unrecognized type\"으로 거부하므로, ProseMirror 트랜잭션으로 직접 삽입한다.\r\n * id는 코어 UniqueID 확장이 누락 노드에 자동 부여한다(types에 columnList/column 포함).\r\n *\r\n * @returns 삽입 성공 여부.\r\n */\r\nexport function insertTwoColumns(\r\n editor: any,\r\n showDivider: boolean = false,\r\n): boolean {\r\n const tiptap = editor?._tiptapEditor;\r\n if (!tiptap) {\r\n return false;\r\n }\r\n const { state, schema } = tiptap;\r\n const { blockContainer, paragraph, column, columnList } = schema.nodes;\r\n if (!blockContainer || !paragraph || !column || !columnList) {\r\n return false;\r\n }\r\n\r\n const $from = state.selection.$from;\r\n\r\n // 컬럼 중첩 금지: 조상에 column/columnList가 있으면 거부(컬럼 안에 columnList를\r\n // 넣으면 column content \"blockContainer+\" 위반 → 삽입이 throw). 안전하게 no-op.\r\n for (let d = $from.depth; d > 0; d--) {\r\n const name = $from.node(d).type.name;\r\n if (name === \"column\" || name === \"columnList\") {\r\n return false;\r\n }\r\n }\r\n\r\n // 현재 selection을 감싸는 blockContainer를 찾아 그 뒤(blockGroup 형제 위치)에 삽입.\r\n let depth = $from.depth;\r\n while (depth > 0 && $from.node(depth).type.name !== \"blockContainer\") {\r\n depth--;\r\n }\r\n if (depth === 0) {\r\n return false;\r\n }\r\n const insertPos = $from.after(depth);\r\n\r\n const mkBlock = () => blockContainer.create(null, paragraph.create());\r\n const mkColumn = () => column.create(null, mkBlock());\r\n // showDivider는 생성 시 고정(가운데 세로 구분선 유무). id는 UniqueID가 자동 부여.\r\n const list = columnList.create({ showDivider }, [mkColumn(), mkColumn()]);\r\n\r\n // insert/dispatch는 위치가 어떤 이유로든 무효면 throw할 수 있으므로 가드한다.\r\n try {\r\n let tr = state.tr.insert(insertPos, list);\r\n // 첫 컬럼의 단락으로 커서 이동(columnList+1, column+1, blockContainer+1, paragraph+1).\r\n try {\r\n tr = tr.setSelection(TextSelection.create(tr.doc, insertPos + 4));\r\n } catch {\r\n // 위치 매핑 실패 시 커서 이동은 생략(삽입 자체는 유효).\r\n }\r\n tiptap.view.dispatch(tr.scrollIntoView());\r\n return true;\r\n } catch {\r\n return false;\r\n }\r\n}\r\n","\"use client\";\r\n\r\nimport {\r\n BasicTextStyleButton,\r\n BlockTypeSelect,\r\n CreateLinkButton,\r\n FileCaptionButton,\r\n FileDeleteButton,\r\n FileRenameButton,\r\n FileReplaceButton,\r\n FileDownloadButton,\r\n FilePreviewButton,\r\n FormattingToolbar,\r\n NestBlockButton,\r\n UnnestBlockButton,\r\n TableCellMergeButton,\r\n} from \"@blocknote/react\";\r\nimport { TextAlignButtonWithVA } from \"./TextAlignButtonWithVA\";\r\nimport { VerticalAlignButton } from \"./VerticalAlignButton\";\r\nimport { TableAlignButton } from \"./TableAlignButton\";\r\nimport { FontSizeButton } from \"./FontSizeButton\";\r\nimport {\r\n LumirColorStyleButton,\r\n LumirCellColorToolbarButton,\r\n} from \"./color/LumirColorControls\";\r\n\r\n/**\r\n * BlockNote 기본 FormattingToolbar + 세로 정렬 버튼(top/middle/bottom)\r\n *\r\n * @see https://www.blocknotejs.org/examples/ui-components/formatting-toolbar-buttons\r\n */\r\nexport const CustomFormattingToolbar = () => {\r\n return (\r\n <FormattingToolbar>\r\n <BlockTypeSelect key={\"blockTypeSelect\"} />\r\n <TableCellMergeButton key={\"tableCellMergeButton\"} />\r\n\r\n <FileCaptionButton key={\"fileCaptionButton\"} />\r\n <FileReplaceButton key={\"replaceFileButton\"} />\r\n <FileRenameButton key={\"fileRenameButton\"} />\r\n <FileDeleteButton key={\"fileDeleteButton\"} />\r\n <FileDownloadButton key={\"fileDownloadButton\"} />\r\n <FilePreviewButton key={\"filePreviewButton\"} />\r\n\r\n <BasicTextStyleButton basicTextStyle={\"bold\"} key={\"boldStyleButton\"} />\r\n <BasicTextStyleButton\r\n basicTextStyle={\"italic\"}\r\n key={\"italicStyleButton\"}\r\n />\r\n <BasicTextStyleButton\r\n basicTextStyle={\"underline\"}\r\n key={\"underlineStyleButton\"}\r\n />\r\n <BasicTextStyleButton\r\n basicTextStyle={\"strike\"}\r\n key={\"strikeStyleButton\"}\r\n />\r\n\r\n <TextAlignButtonWithVA textAlignment={\"left\"} key={\"textAlignLeftButton\"} />\r\n <TextAlignButtonWithVA\r\n textAlignment={\"center\"}\r\n key={\"textAlignCenterButton\"}\r\n />\r\n <TextAlignButtonWithVA textAlignment={\"right\"} key={\"textAlignRightButton\"} />\r\n\r\n {/* 세로 정렬 버튼 (테이블 셀 선택 시에만 표시) */}\r\n <VerticalAlignButton\r\n verticalAlignment=\"top\"\r\n key={\"verticalAlignTop\"}\r\n />\r\n <VerticalAlignButton\r\n verticalAlignment=\"middle\"\r\n key={\"verticalAlignMiddle\"}\r\n />\r\n <VerticalAlignButton\r\n verticalAlignment=\"bottom\"\r\n key={\"verticalAlignBottom\"}\r\n />\r\n\r\n {/* 표 블록 정렬 버튼 (표 컨텍스트에서만 표시) */}\r\n <TableAlignButton alignment=\"left\" key={\"tableAlignLeft\"} />\r\n <TableAlignButton alignment=\"center\" key={\"tableAlignCenter\"} />\r\n <TableAlignButton alignment=\"right\" key={\"tableAlignRight\"} />\r\n\r\n <FontSizeButton key={\"fontSizeButton\"} />\r\n <LumirColorStyleButton key={\"colorStyleButton\"} />\r\n <LumirCellColorToolbarButton key={\"cellColorButton\"} />\r\n <NestBlockButton key={\"nestBlockButton\"} />\r\n <UnnestBlockButton key={\"unnestBlockButton\"} />\r\n <CreateLinkButton key={\"createLinkButton\"} />\r\n </FormattingToolbar>\r\n );\r\n};\r\n","\"use client\";\r\n\r\n/**\r\n * BlockNote 기본 TextAlignButton의 커스텀 버전.\r\n * 테이블 셀에서 가로 정렬 변경 시 ProseMirror 트랜잭션을 직접 사용하여\r\n * selection이 유지되고 verticalAlignment가 초기화되지 않도록 한다.\r\n */\r\n\r\nimport {\r\n BlockSchema,\r\n checkBlockHasDefaultProp,\r\n checkBlockTypeHasDefaultProp,\r\n InlineContentSchema,\r\n mapTableCell,\r\n StyleSchema,\r\n TableContent,\r\n} from \"@blocknote/core\";\r\nimport { useCallback, useMemo } from \"react\";\r\nimport {\r\n useComponentsContext,\r\n useBlockNoteEditor,\r\n useSelectedBlocks,\r\n} from \"@blocknote/react\";\r\nimport { getSelectedCellPositions } from \"../utils/prosemirror-table-utils\";\r\n\r\ntype TextAlignment = \"left\" | \"center\" | \"right\" | \"justify\";\r\n\r\nconst icons: Record<TextAlignment, JSX.Element> = {\r\n left: (\r\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\" width=\"18\" height=\"18\">\r\n <path d=\"M15 15H3v2h12v-2zm0-8H3v2h12V7zM3 13h18v-2H3v2zm0 8h18v-2H3v2zM3 3v2h18V3H3z\" />\r\n </svg>\r\n ),\r\n center: (\r\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\" width=\"18\" height=\"18\">\r\n <path d=\"M7 15v2h10v-2H7zm-4 6h18v-2H3v2zm0-8h18v-2H3v2zm4-6v2h10V7H7zM3 3v2h18V3H3z\" />\r\n </svg>\r\n ),\r\n right: (\r\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\" width=\"18\" height=\"18\">\r\n <path d=\"M3 21h18v-2H3v2zm6-4h12v-2H9v2zm-6-4h18v-2H3v2zm6-4h12V7H9v2zM3 3v2h18V3H3z\" />\r\n </svg>\r\n ),\r\n justify: (\r\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\" width=\"18\" height=\"18\">\r\n <path d=\"M3 21h18v-2H3v2zm0-4h18v-2H3v2zm0-4h18v-2H3v2zm0-4h18V7H3v2zM3 3v2h18V3H3z\" />\r\n </svg>\r\n ),\r\n};\r\n\r\nconst tooltipMap: Record<TextAlignment, string> = {\r\n left: \"왼쪽 정렬\",\r\n center: \"가운데 정렬\",\r\n right: \"오른쪽 정렬\",\r\n justify: \"양쪽 정렬\",\r\n};\r\n\r\nexport const TextAlignButtonWithVA = (props: {\r\n textAlignment: TextAlignment;\r\n}) => {\r\n const Components = useComponentsContext()!;\r\n const editor = useBlockNoteEditor<\r\n BlockSchema,\r\n InlineContentSchema,\r\n StyleSchema\r\n >();\r\n\r\n const selectedBlocks = useSelectedBlocks(editor);\r\n\r\n const textAlignment = useMemo(() => {\r\n const block = selectedBlocks[0];\r\n\r\n if (checkBlockHasDefaultProp(\"textAlignment\", block, editor)) {\r\n return block.props.textAlignment;\r\n }\r\n if (block.type === \"table\") {\r\n const cellSelection = editor.tableHandles?.getCellSelection();\r\n if (!cellSelection) {\r\n return;\r\n }\r\n const allCellsInTable = cellSelection.cells.map(\r\n ({ row, col }: { row: number; col: number }) =>\r\n mapTableCell(\r\n (block.content as TableContent<any, any>).rows[row].cells[col],\r\n ).props.textAlignment,\r\n );\r\n const firstAlignment = allCellsInTable[0];\r\n\r\n if (allCellsInTable.every((alignment: string) => alignment === firstAlignment)) {\r\n return firstAlignment;\r\n }\r\n }\r\n\r\n return;\r\n }, [editor, selectedBlocks]);\r\n\r\n const setTextAlignment = useCallback(\r\n (newAlignment: TextAlignment) => {\r\n editor.focus();\r\n\r\n for (const block of selectedBlocks) {\r\n if (block.type === \"table\") {\r\n const tiptap = editor._tiptapEditor;\r\n if (!tiptap) continue;\r\n\r\n const positions = getSelectedCellPositions(editor);\r\n if (positions.length === 0) continue;\r\n\r\n const { state } = tiptap;\r\n let tr = state.tr;\r\n\r\n for (const pos of positions) {\r\n const node = tr.doc.nodeAt(pos);\r\n if (node) {\r\n tr = tr.setNodeMarkup(pos, undefined, {\r\n ...node.attrs,\r\n textAlignment: newAlignment,\r\n });\r\n }\r\n }\r\n\r\n tiptap.view?.dispatch(tr);\r\n } else if (checkBlockTypeHasDefaultProp(\"textAlignment\", block.type, editor)) {\r\n editor.updateBlock(block, {\r\n props: { textAlignment: newAlignment },\r\n });\r\n }\r\n }\r\n },\r\n [editor, selectedBlocks],\r\n );\r\n\r\n const show = useMemo(() => {\r\n return !!selectedBlocks.find(\r\n (block) =>\r\n \"textAlignment\" in block.props ||\r\n (block.type === \"table\" && block.children),\r\n );\r\n }, [selectedBlocks]);\r\n\r\n if (!show || !editor.isEditable) {\r\n return null;\r\n }\r\n\r\n return (\r\n <Components.FormattingToolbar.Button\r\n className={\"bn-button\"}\r\n data-test={`alignText${\r\n props.textAlignment.slice(0, 1).toUpperCase() +\r\n props.textAlignment.slice(1)\r\n }`}\r\n onClick={() => setTextAlignment(props.textAlignment)}\r\n isSelected={textAlignment === props.textAlignment}\r\n label={tooltipMap[props.textAlignment]}\r\n mainTooltip={tooltipMap[props.textAlignment]}\r\n icon={icons[props.textAlignment]}\r\n />\r\n );\r\n};\r\n","\"use client\";\r\n\r\nimport { useCallback, useMemo } from \"react\";\r\nimport {\r\n useBlockNoteEditor,\r\n useComponentsContext,\r\n useSelectedBlocks,\r\n} from \"@blocknote/react\";\r\nimport type {\r\n BlockSchema,\r\n InlineContentSchema,\r\n StyleSchema,\r\n} from \"@blocknote/core\";\r\nimport type { VerticalAlignment } from \"../extensions/VerticalAlignmentExtension\";\r\nimport { getSelectedCellPositions } from \"../utils/prosemirror-table-utils\";\r\n\r\nconst icons: Record<VerticalAlignment, JSX.Element> = {\r\n top: (\r\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.5\">\r\n <rect x=\"2\" y=\"2\" width=\"12\" height=\"12\" rx=\"1\" />\r\n <line x1=\"5\" y1=\"5\" x2=\"11\" y2=\"5\" />\r\n </svg>\r\n ),\r\n middle: (\r\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.5\">\r\n <rect x=\"2\" y=\"2\" width=\"12\" height=\"12\" rx=\"1\" />\r\n <line x1=\"5\" y1=\"8\" x2=\"11\" y2=\"8\" />\r\n </svg>\r\n ),\r\n bottom: (\r\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.5\">\r\n <rect x=\"2\" y=\"2\" width=\"12\" height=\"12\" rx=\"1\" />\r\n <line x1=\"5\" y1=\"11\" x2=\"11\" y2=\"11\" />\r\n </svg>\r\n ),\r\n};\r\n\r\nconst tooltips: Record<VerticalAlignment, string> = {\r\n top: \"위쪽 정렬\",\r\n middle: \"세로 가운데 정렬\",\r\n bottom: \"아래쪽 정렬\",\r\n};\r\n\r\nfunction getCurrentVerticalAlignment(editor: any): VerticalAlignment | undefined {\r\n const tiptap = editor._tiptapEditor;\r\n if (!tiptap) return undefined;\r\n\r\n const positions = getSelectedCellPositions(editor);\r\n if (positions.length === 0) return undefined;\r\n\r\n const { state } = tiptap;\r\n const alignments = positions.map((pos) => {\r\n const node = state.doc.nodeAt(pos);\r\n return (node?.attrs?.verticalAlignment as VerticalAlignment) || \"top\";\r\n });\r\n\r\n const first = alignments[0];\r\n return alignments.every((a) => a === first) ? first : undefined;\r\n}\r\n\r\nexport const VerticalAlignButton = (props: {\r\n verticalAlignment: VerticalAlignment;\r\n}) => {\r\n const Components = useComponentsContext()!;\r\n const editor = useBlockNoteEditor<\r\n BlockSchema,\r\n InlineContentSchema,\r\n StyleSchema\r\n >();\r\n\r\n const selectedBlocks = useSelectedBlocks(editor);\r\n\r\n const currentAlignment = useMemo(() => {\r\n return getCurrentVerticalAlignment(editor);\r\n // eslint-disable-next-line react-hooks/exhaustive-deps\r\n }, [editor, selectedBlocks]);\r\n\r\n const setVerticalAlignment = useCallback(\r\n (alignment: VerticalAlignment) => {\r\n const tiptap = editor._tiptapEditor;\r\n if (!tiptap) return;\r\n\r\n const positions = getSelectedCellPositions(editor);\r\n if (positions.length === 0) return;\r\n\r\n editor.focus();\r\n\r\n const { state } = tiptap;\r\n let tr = state.tr;\r\n\r\n for (const pos of positions) {\r\n const node = tr.doc.nodeAt(pos);\r\n if (node) {\r\n tr = tr.setNodeMarkup(pos, undefined, {\r\n ...node.attrs,\r\n verticalAlignment: alignment,\r\n });\r\n }\r\n }\r\n\r\n tiptap.view?.dispatch(tr);\r\n },\r\n [editor],\r\n );\r\n\r\n const isInTable = useMemo(() => {\r\n return selectedBlocks.some((block) => block.type === \"table\");\r\n }, [selectedBlocks]);\r\n\r\n if (!isInTable || !editor.isEditable) {\r\n return null;\r\n }\r\n\r\n return (\r\n <Components.FormattingToolbar.Button\r\n className={\"bn-button\"}\r\n data-test={`verticalAlign${props.verticalAlignment.charAt(0).toUpperCase() + props.verticalAlignment.slice(1)}`}\r\n onClick={() => setVerticalAlignment(props.verticalAlignment)}\r\n isSelected={currentAlignment === props.verticalAlignment}\r\n label={tooltips[props.verticalAlignment]}\r\n mainTooltip={tooltips[props.verticalAlignment]}\r\n icon={icons[props.verticalAlignment]}\r\n />\r\n );\r\n};\r\n","\"use client\";\r\n\r\n/**\r\n * 표(table) 블록 전체를 에디터 영역 기준으로 좌/가운데/우 정렬하는 툴바 버튼.\r\n * 셀 텍스트 정렬(TextAlignButtonWithVA)과 구분되며, 표 블록이 선택된 컨텍스트에서만 노출된다.\r\n * 정렬값은 table 노드 attr(tableAlignment)에 저장한다(setTableAlignment).\r\n */\r\n\r\nimport { useCallback, useMemo } from \"react\";\r\nimport {\r\n useBlockNoteEditor,\r\n useComponentsContext,\r\n useSelectedBlocks,\r\n} from \"@blocknote/react\";\r\nimport type {\r\n BlockSchema,\r\n InlineContentSchema,\r\n StyleSchema,\r\n} from \"@blocknote/core\";\r\nimport type { TableAlignment } from \"../extensions/TableAlignmentExtension\";\r\nimport {\r\n getTableAlignment,\r\n setTableAlignment,\r\n} from \"../utils/prosemirror-table-utils\";\r\n\r\n// 표(직사각형) 안에 정렬 위치를 나타내는 작은 막대를 둔 아이콘.\r\nconst icons: Record<TableAlignment, JSX.Element> = {\r\n left: (\r\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.4\">\r\n <rect x=\"1.5\" y=\"4\" width=\"7\" height=\"8\" rx=\"1\" fill=\"currentColor\" stroke=\"none\" />\r\n <line x1=\"1.5\" y1=\"1.5\" x2=\"14.5\" y2=\"1.5\" />\r\n <line x1=\"1.5\" y1=\"14.5\" x2=\"14.5\" y2=\"14.5\" />\r\n </svg>\r\n ),\r\n center: (\r\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.4\">\r\n <rect x=\"4.5\" y=\"4\" width=\"7\" height=\"8\" rx=\"1\" fill=\"currentColor\" stroke=\"none\" />\r\n <line x1=\"1.5\" y1=\"1.5\" x2=\"14.5\" y2=\"1.5\" />\r\n <line x1=\"1.5\" y1=\"14.5\" x2=\"14.5\" y2=\"14.5\" />\r\n </svg>\r\n ),\r\n right: (\r\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.4\">\r\n <rect x=\"7.5\" y=\"4\" width=\"7\" height=\"8\" rx=\"1\" fill=\"currentColor\" stroke=\"none\" />\r\n <line x1=\"1.5\" y1=\"1.5\" x2=\"14.5\" y2=\"1.5\" />\r\n <line x1=\"1.5\" y1=\"14.5\" x2=\"14.5\" y2=\"14.5\" />\r\n </svg>\r\n ),\r\n};\r\n\r\nconst tooltips: Record<TableAlignment, string> = {\r\n left: \"표 왼쪽 정렬\",\r\n center: \"표 가운데 정렬\",\r\n right: \"표 오른쪽 정렬\",\r\n};\r\n\r\nexport const TableAlignButton = (props: { alignment: TableAlignment }) => {\r\n const Components = useComponentsContext()!;\r\n const editor = useBlockNoteEditor<\r\n BlockSchema,\r\n InlineContentSchema,\r\n StyleSchema\r\n >();\r\n\r\n const selectedBlocks = useSelectedBlocks(editor);\r\n\r\n const tableBlock = useMemo(\r\n () => selectedBlocks.find((block) => block.type === \"table\"),\r\n [selectedBlocks],\r\n );\r\n\r\n const current = useMemo(() => {\r\n if (!tableBlock?.id) return \"left\";\r\n return getTableAlignment(editor, tableBlock.id);\r\n // eslint-disable-next-line react-hooks/exhaustive-deps\r\n }, [editor, tableBlock, selectedBlocks]);\r\n\r\n const apply = useCallback(() => {\r\n if (!tableBlock?.id) return;\r\n editor.focus();\r\n setTableAlignment(editor, tableBlock.id, props.alignment);\r\n }, [editor, tableBlock, props.alignment]);\r\n\r\n if (!tableBlock || !editor.isEditable) {\r\n return null;\r\n }\r\n\r\n return (\r\n <Components.FormattingToolbar.Button\r\n className={\"bn-button\"}\r\n data-test={`tableAlign${props.alignment.charAt(0).toUpperCase() + props.alignment.slice(1)}`}\r\n onClick={apply}\r\n isSelected={current === props.alignment}\r\n label={tooltips[props.alignment]}\r\n mainTooltip={tooltips[props.alignment]}\r\n icon={icons[props.alignment]}\r\n />\r\n );\r\n};\r\n","\"use client\";\r\n\r\n/**\r\n * 포매팅 툴바의 \"글자 크기\" 드롭다운 버튼.\r\n * LumirColorStyleButton(color/LumirColorControls.tsx)과 동일한 패턴:\r\n * Generic.Menu 드롭다운 + addStyles/removeStyles + 다음 틱 focus 복원.\r\n */\r\n\r\nimport {\r\n type BlockSchema,\r\n type InlineContentSchema,\r\n type StyleSchema,\r\n} from \"@blocknote/core\";\r\nimport {\r\n useBlockNoteEditor,\r\n useComponentsContext,\r\n useEditorContentOrSelectionChange,\r\n useSelectedBlocks,\r\n} from \"@blocknote/react\";\r\nimport { useCallback, useMemo, useState } from \"react\";\r\nimport { FONT_SIZE_PRESETS } from \"../styles/FontSizeStyle\";\r\n\r\nconst DEFAULT_LABEL = \"기본\";\r\n\r\n/** \"18px\" → \"18\" (드롭다운/트리거 표시용) */\r\nconst toLabel = (size: string) => size.replace(/px$/, \"\");\r\n\r\nfunction FontSizeIcon({ size }: { size: string }) {\r\n return (\r\n <span\r\n style={{\r\n pointerEvents: \"none\",\r\n fontSize: \"12px\",\r\n fontWeight: 600,\r\n lineHeight: \"20px\",\r\n whiteSpace: \"nowrap\",\r\n }}\r\n >\r\n {size ? toLabel(size) : \"가A\"}\r\n </span>\r\n );\r\n}\r\n\r\nexport function FontSizeButton() {\r\n const Components = useComponentsContext()!;\r\n const editor = useBlockNoteEditor<\r\n BlockSchema,\r\n InlineContentSchema,\r\n StyleSchema\r\n >();\r\n\r\n // 커스텀 스타일은 제네릭 StyleSchema에서 추론되지 않으므로 느슨한 타입으로 처리\r\n // (LumirColorStyleButton과 동일한 관례)\r\n const ed = editor as unknown as {\r\n getActiveStyles: () => Record<string, string>;\r\n addStyles: (styles: Record<string, string>) => void;\r\n removeStyles: (styles: Record<string, string>) => void;\r\n };\r\n\r\n const styleSchema = editor.schema.styleSchema as Record<\r\n string,\r\n { type?: string; propSchema?: string }\r\n >;\r\n const fontSizeInSchema =\r\n styleSchema.fontSize?.type === \"fontSize\" &&\r\n styleSchema.fontSize?.propSchema === \"string\";\r\n\r\n const selectedBlocks = useSelectedBlocks(editor);\r\n\r\n const [currentSize, setCurrentSize] = useState<string>(\r\n fontSizeInSchema ? ed.getActiveStyles().fontSize || \"\" : \"\",\r\n );\r\n\r\n useEditorContentOrSelectionChange(() => {\r\n if (fontSizeInSchema) {\r\n setCurrentSize(ed.getActiveStyles().fontSize || \"\");\r\n }\r\n }, editor);\r\n\r\n const setFontSize = useCallback(\r\n (size: string) => {\r\n size === \"\"\r\n ? ed.removeStyles({ fontSize: \"\" })\r\n : ed.addStyles({ fontSize: size });\r\n // Mantine Toolbar의 useFocusTrap 호환을 위해 다음 틱에 focus.\r\n setTimeout(() => editor.focus());\r\n },\r\n // eslint-disable-next-line react-hooks/exhaustive-deps\r\n [editor],\r\n );\r\n\r\n const show = useMemo(() => {\r\n if (!fontSizeInSchema) {\r\n return false;\r\n }\r\n for (const block of selectedBlocks) {\r\n if (block.content !== undefined) {\r\n return true;\r\n }\r\n }\r\n return false;\r\n }, [fontSizeInSchema, selectedBlocks]);\r\n\r\n if (!show || !editor.isEditable) {\r\n return null;\r\n }\r\n\r\n const tooltip = \"글자 크기\";\r\n\r\n return (\r\n <Components.Generic.Menu.Root>\r\n <Components.Generic.Menu.Trigger>\r\n <Components.FormattingToolbar.Button\r\n className={\"bn-button\"}\r\n data-test=\"font-size\"\r\n label={tooltip}\r\n mainTooltip={tooltip}\r\n icon={<FontSizeIcon size={currentSize} />}\r\n />\r\n </Components.Generic.Menu.Trigger>\r\n <Components.Generic.Menu.Dropdown className={\"bn-menu-dropdown\"}>\r\n <Components.Generic.Menu.Label>글자 크기</Components.Generic.Menu.Label>\r\n <Components.Generic.Menu.Item\r\n onClick={() => setFontSize(\"\")}\r\n checked={currentSize === \"\"}\r\n data-test={\"font-size-default\"}\r\n key={\"font-size-default\"}\r\n >\r\n {DEFAULT_LABEL}\r\n </Components.Generic.Menu.Item>\r\n {FONT_SIZE_PRESETS.map((size) => (\r\n <Components.Generic.Menu.Item\r\n onClick={() => setFontSize(size)}\r\n checked={currentSize === size}\r\n data-test={\"font-size-\" + toLabel(size)}\r\n key={\"font-size-\" + size}\r\n >\r\n {toLabel(size)}\r\n </Components.Generic.Menu.Item>\r\n ))}\r\n </Components.Generic.Menu.Dropdown>\r\n </Components.Generic.Menu.Root>\r\n );\r\n}\r\n","\"use client\";\r\n\r\n/**\r\n * 색상 컨트롤(텍스트 색/배경) — 컨텍스트별로 그룹 제목·툴팁을 명확히 구분한다.\r\n *\r\n * 배경:\r\n * - BlockNote의 공유 `ColorPicker`는 그룹 제목을 editor 전역 dictionary\r\n * (`color_picker.text_title`/`background_title`)에서 읽으므로, \"글(텍스트) 배경\"과\r\n * \"셀 배경\"을 같은 \"텍스트/배경\" 라벨로 표기해 어떤 배경을 바꾸는 버튼인지 모호했다.\r\n * - 공유 `ColorPicker`/`ColorIcon`은 패키지 루트로 export되지 않아 dictionary를\r\n * 덮어쓸 수도, 직접 import할 수도 없다(dictionary는 editor 인스턴스에 묶임).\r\n * → 그래서 `ColorPicker`를 제목 prop을 받도록 복제(`LumirColorPicker`)하고,\r\n * 툴바(인라인)·셀 메뉴용 버튼을 각각 만들어 컨텍스트별 라벨을 주입한다.\r\n *\r\n * 라벨 규칙:\r\n * - 툴바(인라인 글자 색/배경): 툴팁 \"텍스트 색·배경\", 그룹 \"텍스트 색\" / \"텍스트 배경\"\r\n * - 셀 메뉴(셀 글자 색/배경): 트리거 \"셀 색·배경\", 그룹 \"셀 글자색\" / \"셀 배경\"\r\n * 버튼은 색·배경을 모두 바꾸므로 툴팁을 \"배경\"만으로 적으면 색 기능이 가려지고\r\n * (특히 셀 메뉴의 텍스트 색), 그룹 제목에 \"텍스트 배경\"/\"셀 배경\"을 명시해 두 배경\r\n * 기능을 분명히 구분한다.\r\n */\r\n\r\nimport {\r\n type BlockSchema,\r\n type DefaultBlockSchema,\r\n type DefaultInlineContentSchema,\r\n type DefaultStyleSchema,\r\n type InlineContentSchema,\r\n isTableCell,\r\n mapTableCell,\r\n type StyleSchema,\r\n} from \"@blocknote/core\";\r\nimport {\r\n SplitButton,\r\n type TableCellMenuProps,\r\n useBlockNoteEditor,\r\n useComponentsContext,\r\n useDictionary,\r\n useEditorContentOrSelectionChange,\r\n useSelectedBlocks,\r\n} from \"@blocknote/react\";\r\nimport { type ReactNode, useCallback, useMemo, useRef, useState } from \"react\";\r\nimport {\r\n getSelectedCellPositions,\r\n setCellAttrAtPositions,\r\n} from \"../../utils/prosemirror-table-utils\";\r\n\r\nconst COLORS = [\r\n \"default\",\r\n \"gray\",\r\n \"brown\",\r\n \"red\",\r\n \"orange\",\r\n \"yellow\",\r\n \"green\",\r\n \"blue\",\r\n \"purple\",\r\n \"pink\",\r\n] as const;\r\n\r\n/** BlockNote 내부 ColorIcon이 export되지 않아 동일 구현(색 스와치 \"A\")을 복제. */\r\nfunction ColorIcon(props: {\r\n textColor?: string;\r\n backgroundColor?: string;\r\n size?: number;\r\n}) {\r\n const textColor = props.textColor || \"default\";\r\n const backgroundColor = props.backgroundColor || \"default\";\r\n const size = props.size || 16;\r\n return (\r\n <div\r\n className={\"bn-color-icon\"}\r\n data-background-color={backgroundColor}\r\n data-text-color={textColor}\r\n style={{\r\n pointerEvents: \"none\",\r\n fontSize: `${size * 0.75}px`,\r\n height: `${size}px`,\r\n lineHeight: `${size}px`,\r\n textAlign: \"center\",\r\n width: `${size}px`,\r\n }}\r\n >\r\n A\r\n </div>\r\n );\r\n}\r\n\r\n/** 셀 채움(배경) 아이콘 — 인라인 \"A\" 아이콘과 구분되는 페인트통(fill) 모양. */\r\nfunction CellFillIcon({ size = 18 }: { size?: number }) {\r\n return (\r\n <svg\r\n width={size}\r\n height={size}\r\n viewBox=\"0 0 24 24\"\r\n fill=\"currentColor\"\r\n style={{ pointerEvents: \"none\" }}\r\n aria-hidden=\"true\"\r\n >\r\n <path d=\"M16.56 8.94 7.62 0 6.21 1.41l2.38 2.38-5.15 5.15c-.59.59-.59 1.54 0 2.12l5.5 5.5c.29.29.68.44 1.06.44s.77-.15 1.06-.44l5.5-5.5c.59-.58.59-1.53 0-2.12zM5.21 10 10 5.21 14.79 10H5.21zM19 11.5s-2 2.17-2 3.5c0 1.1.9 2 2 2s2-.9 2-2c0-1.33-2-3.5-2-3.5z\" />\r\n </svg>\r\n );\r\n}\r\n\r\n/**\r\n * 공유 ColorPicker의 복제판. 그룹 제목을 `textTitle`/`backgroundTitle`로 주입할 수\r\n * 있다(미지정 시 dictionary 기본값). data-test는 원본과 동일하게 유지.\r\n */\r\nfunction LumirColorPicker(props: {\r\n onClick?: () => void;\r\n iconSize?: number;\r\n textTitle?: string;\r\n backgroundTitle?: string;\r\n text?: { color: string; setColor: (color: string) => void };\r\n background?: { color: string; setColor: (color: string) => void };\r\n}) {\r\n const Components = useComponentsContext()!;\r\n const dict = useDictionary();\r\n\r\n return (\r\n <>\r\n {props.text ? (\r\n <>\r\n <Components.Generic.Menu.Label>\r\n {props.textTitle ?? dict.color_picker.text_title}\r\n </Components.Generic.Menu.Label>\r\n {COLORS.map((color) => (\r\n <Components.Generic.Menu.Item\r\n onClick={() => {\r\n props.onClick?.();\r\n props.text!.setColor(color);\r\n }}\r\n data-test={\"text-color-\" + color}\r\n icon={<ColorIcon textColor={color} size={props.iconSize} />}\r\n checked={props.text!.color === color}\r\n key={\"text-color-\" + color}\r\n >\r\n {dict.color_picker.colors[color]}\r\n </Components.Generic.Menu.Item>\r\n ))}\r\n </>\r\n ) : null}\r\n {props.background ? (\r\n <>\r\n <Components.Generic.Menu.Label>\r\n {props.backgroundTitle ?? dict.color_picker.background_title}\r\n </Components.Generic.Menu.Label>\r\n {COLORS.map((color) => (\r\n <Components.Generic.Menu.Item\r\n onClick={() => {\r\n props.onClick?.();\r\n props.background!.setColor(color);\r\n }}\r\n data-test={\"background-color-\" + color}\r\n icon={\r\n <ColorIcon backgroundColor={color} size={props.iconSize} />\r\n }\r\n checked={props.background!.color === color}\r\n key={\"background-color-\" + color}\r\n >\r\n {dict.color_picker.colors[color]}\r\n </Components.Generic.Menu.Item>\r\n ))}\r\n </>\r\n ) : null}\r\n </>\r\n );\r\n}\r\n\r\n/**\r\n * 툴바(인라인 텍스트) 색상 버튼. 글자 색 + 글자 배경을 바꾼다.\r\n * BlockNote ColorStyleButton의 복제판이며 그룹 제목/툴팁만 \"텍스트\"로 명시한다.\r\n */\r\nexport function LumirColorStyleButton() {\r\n const Components = useComponentsContext()!;\r\n const editor = useBlockNoteEditor<\r\n BlockSchema,\r\n InlineContentSchema,\r\n StyleSchema\r\n >();\r\n\r\n // 기본 스타일 스키마(textColor/backgroundColor)는 제네릭 StyleSchema에서 모두\r\n // undefined로 추론되므로, 스타일 읽기/쓰기는 느슨한 타입 별칭으로 처리한다.\r\n const ed = editor as unknown as {\r\n getActiveStyles: () => Record<string, string>;\r\n addStyles: (styles: Record<string, string>) => void;\r\n removeStyles: (styles: Record<string, string>) => void;\r\n };\r\n\r\n const styleSchema = editor.schema.styleSchema as Record<\r\n string,\r\n { type?: string; propSchema?: string }\r\n >;\r\n const textColorInSchema =\r\n styleSchema.textColor?.type === \"textColor\" &&\r\n styleSchema.textColor?.propSchema === \"string\";\r\n const backgroundColorInSchema =\r\n styleSchema.backgroundColor?.type === \"backgroundColor\" &&\r\n styleSchema.backgroundColor?.propSchema === \"string\";\r\n\r\n const selectedBlocks = useSelectedBlocks(editor);\r\n\r\n const [currentTextColor, setCurrentTextColor] = useState<string>(\r\n textColorInSchema ? ed.getActiveStyles().textColor || \"default\" : \"default\",\r\n );\r\n const [currentBackgroundColor, setCurrentBackgroundColor] = useState<string>(\r\n backgroundColorInSchema\r\n ? ed.getActiveStyles().backgroundColor || \"default\"\r\n : \"default\",\r\n );\r\n\r\n useEditorContentOrSelectionChange(() => {\r\n const active = ed.getActiveStyles();\r\n if (textColorInSchema) {\r\n setCurrentTextColor(active.textColor || \"default\");\r\n }\r\n if (backgroundColorInSchema) {\r\n setCurrentBackgroundColor(active.backgroundColor || \"default\");\r\n }\r\n }, editor);\r\n\r\n const setTextColor = useCallback(\r\n (color: string) => {\r\n color === \"default\"\r\n ? ed.removeStyles({ textColor: color })\r\n : ed.addStyles({ textColor: color });\r\n // Mantine Toolbar의 useFocusTrap 호환을 위해 다음 틱에 focus.\r\n setTimeout(() => editor.focus());\r\n },\r\n // eslint-disable-next-line react-hooks/exhaustive-deps\r\n [editor],\r\n );\r\n\r\n const setBackgroundColor = useCallback(\r\n (color: string) => {\r\n color === \"default\"\r\n ? ed.removeStyles({ backgroundColor: color })\r\n : ed.addStyles({ backgroundColor: color });\r\n setTimeout(() => editor.focus());\r\n },\r\n // eslint-disable-next-line react-hooks/exhaustive-deps\r\n [editor],\r\n );\r\n\r\n const show = useMemo(() => {\r\n if (!textColorInSchema && !backgroundColorInSchema) {\r\n return false;\r\n }\r\n for (const block of selectedBlocks) {\r\n if (block.content !== undefined) {\r\n return true;\r\n }\r\n }\r\n return false;\r\n }, [backgroundColorInSchema, selectedBlocks, textColorInSchema]);\r\n\r\n if (!show || !editor.isEditable) {\r\n return null;\r\n }\r\n\r\n const tooltip = \"텍스트 색·배경\";\r\n\r\n return (\r\n <Components.Generic.Menu.Root>\r\n <Components.Generic.Menu.Trigger>\r\n <Components.FormattingToolbar.Button\r\n className={\"bn-button\"}\r\n data-test=\"colors\"\r\n label={tooltip}\r\n mainTooltip={tooltip}\r\n icon={\r\n <ColorIcon\r\n textColor={currentTextColor}\r\n backgroundColor={currentBackgroundColor}\r\n size={20}\r\n />\r\n }\r\n />\r\n </Components.Generic.Menu.Trigger>\r\n <Components.Generic.Menu.Dropdown\r\n className={\"bn-menu-dropdown bn-color-picker-dropdown\"}\r\n >\r\n <LumirColorPicker\r\n textTitle=\"텍스트 색\"\r\n backgroundTitle=\"텍스트 배경\"\r\n text={\r\n textColorInSchema\r\n ? { color: currentTextColor, setColor: setTextColor }\r\n : undefined\r\n }\r\n background={\r\n backgroundColorInSchema\r\n ? { color: currentBackgroundColor, setColor: setBackgroundColor }\r\n : undefined\r\n }\r\n />\r\n </Components.Generic.Menu.Dropdown>\r\n </Components.Generic.Menu.Root>\r\n );\r\n}\r\n\r\n/**\r\n * 포매팅 툴바의 \"셀 배경색\" 버튼 — 여러 셀을 드래그(DND)로 선택했을 때 선택된\r\n * 모든 셀의 **배경색**을 일괄 변경한다(병합 버튼과 동일하게 다중 셀 선택에서만\r\n * 노출). 단일 셀은 셀 우측 그립 메뉴가 담당하므로 여기서는 숨긴다.\r\n *\r\n * 적용은 `setNodeMarkup`(셀 노드 attr) 방식이라 **셀 선택이 유지**된다 → 연속\r\n * 재색칠 가능(Notion 동작). updateBlock과 달리 내용 전체를 교체하지 않는다.\r\n *\r\n * 주의: 색 스와치 클릭은 드롭다운 상호작용이므로, 메뉴가 열리는 순간 셀 위치를\r\n * 스냅샷해 둔다. 적용 시 라이브 선택을 우선 쓰되(선택 유지), 혹시 붕괴했으면\r\n * 스냅샷으로 폴백해 색은 정확히 적용한다.\r\n *\r\n * 한계: 병합 셀(colspan/rowspan)은 이번 범위 외(예외만 방지).\r\n */\r\nexport function LumirCellColorToolbarButton() {\r\n const Components = useComponentsContext()!;\r\n const editor = useBlockNoteEditor<\r\n { table: DefaultBlockSchema[\"table\"] },\r\n InlineContentSchema,\r\n StyleSchema\r\n >();\r\n const selectedBlocks = useSelectedBlocks(editor);\r\n\r\n // 다중 셀 선택 여부(병합 버튼과 동일 패턴: selectedBlocks 변화 시 재계산).\r\n const isMultiCell = useMemo(() => {\r\n if (selectedBlocks.length !== 1 || selectedBlocks[0].type !== \"table\") {\r\n return false;\r\n }\r\n const cs = editor.tableHandles?.getCellSelection();\r\n return !!cs && cs.cells.length > 1;\r\n }, [editor, selectedBlocks]);\r\n\r\n // 메뉴 열림 시점의 셀 위치(pos) 스냅샷.\r\n const stashRef = useRef<number[]>([]);\r\n\r\n const applyBackground = useCallback(\r\n (color: string) => {\r\n // 라이브 선택 우선(선택 유지), 비어 있으면 스냅샷 폴백(색은 정확히 적용).\r\n const live = getSelectedCellPositions(editor);\r\n const positions = live.length > 0 ? live : stashRef.current;\r\n setCellAttrAtPositions(editor, positions, \"backgroundColor\", color);\r\n // 셀 선택이 다시 보이도록 에디터로 포커스 복귀.\r\n setTimeout(() => editor.focus());\r\n },\r\n [editor],\r\n );\r\n\r\n if (!editor.isEditable || !isMultiCell) {\r\n return null;\r\n }\r\n\r\n const tooltip = \"셀 배경색\";\r\n\r\n return (\r\n <Components.Generic.Menu.Root\r\n onOpenChange={(open: boolean) => {\r\n // 드롭다운이 열리는 순간 셀 위치 스냅샷(상호작용 중 선택 붕괴 대비).\r\n if (open) {\r\n stashRef.current = getSelectedCellPositions(editor);\r\n }\r\n }}\r\n >\r\n <Components.Generic.Menu.Trigger>\r\n <Components.FormattingToolbar.Button\r\n className={\"bn-button\"}\r\n data-test=\"cell-colors\"\r\n label={tooltip}\r\n mainTooltip={tooltip}\r\n icon={<CellFillIcon size={18} />}\r\n />\r\n </Components.Generic.Menu.Trigger>\r\n <Components.Generic.Menu.Dropdown\r\n className={\"bn-menu-dropdown bn-color-picker-dropdown\"}\r\n >\r\n {/* 일괄 변경은 배경색만 제공(글자색 그룹 제거). */}\r\n <LumirColorPicker\r\n backgroundTitle=\"셀 배경\"\r\n background={{ color: \"default\", setColor: applyBackground }}\r\n />\r\n </Components.Generic.Menu.Dropdown>\r\n </Components.Generic.Menu.Root>\r\n );\r\n}\r\n\r\n/**\r\n * 셀 메뉴의 색상 서브메뉴. 셀 글자 색 + 셀 배경을 바꾼다.\r\n * BlockNote 셀 ColorPickerButton의 복제판이며 트리거/그룹 제목만 \"셀\"로 명시한다.\r\n */\r\nfunction LumirCellColorPickerButton<\r\n I extends InlineContentSchema = DefaultInlineContentSchema,\r\n S extends StyleSchema = DefaultStyleSchema,\r\n>(props: TableCellMenuProps<I, S>) {\r\n const Components = useComponentsContext()!;\r\n const editor = useBlockNoteEditor<\r\n { table: DefaultBlockSchema[\"table\"] },\r\n I,\r\n S\r\n >();\r\n\r\n const updateColor = (color: string, type: \"text\" | \"background\") => {\r\n const newTable = props.block.content.rows.map((row) => ({\r\n ...row,\r\n cells: row.cells.map((cell) => mapTableCell(cell)),\r\n }));\r\n\r\n if (type === \"text\") {\r\n newTable[props.rowIndex].cells[props.colIndex].props.textColor = color;\r\n } else {\r\n newTable[props.rowIndex].cells[props.colIndex].props.backgroundColor =\r\n color;\r\n }\r\n\r\n editor.updateBlock(props.block, {\r\n type: \"table\",\r\n content: { ...props.block.content, rows: newTable },\r\n });\r\n // updateBlock이 선택을 블록 밖으로 옮기므로 커서를 블록으로 되돌린다.\r\n editor.setTextCursorPosition(props.block);\r\n };\r\n\r\n const currentCell =\r\n props.block.content.rows[props.rowIndex]?.cells?.[props.colIndex];\r\n\r\n if (\r\n !currentCell ||\r\n (editor.settings.tables.cellTextColor === false &&\r\n editor.settings.tables.cellBackgroundColor === false)\r\n ) {\r\n return null;\r\n }\r\n\r\n return (\r\n <Components.Generic.Menu.Root position={\"right\"} sub={true}>\r\n <Components.Generic.Menu.Trigger sub={true}>\r\n <Components.Generic.Menu.Item className={\"bn-menu-item\"} subTrigger={true}>\r\n 셀 색·배경\r\n </Components.Generic.Menu.Item>\r\n </Components.Generic.Menu.Trigger>\r\n\r\n <Components.Generic.Menu.Dropdown\r\n sub={true}\r\n className={\"bn-menu-dropdown bn-color-picker-dropdown\"}\r\n >\r\n <LumirColorPicker\r\n iconSize={18}\r\n textTitle=\"셀 글자색\"\r\n backgroundTitle=\"셀 배경\"\r\n text={\r\n editor.settings.tables.cellTextColor\r\n ? {\r\n color: isTableCell(currentCell)\r\n ? currentCell.props.textColor\r\n : \"default\",\r\n setColor: (color) => updateColor(color, \"text\"),\r\n }\r\n : undefined\r\n }\r\n background={\r\n editor.settings.tables.cellBackgroundColor\r\n ? {\r\n color: isTableCell(currentCell)\r\n ? currentCell.props.backgroundColor\r\n : \"default\",\r\n setColor: (color) => updateColor(color, \"background\"),\r\n }\r\n : undefined\r\n }\r\n />\r\n </Components.Generic.Menu.Dropdown>\r\n </Components.Generic.Menu.Root>\r\n );\r\n}\r\n\r\n/**\r\n * 셀 핸들 드롭다운 메뉴. 기본 TableCellMenu 복제판으로, 색상 버튼만 \"셀\" 라벨이\r\n * 명시된 LumirCellColorPickerButton으로 교체한다(병합 버튼은 그대로).\r\n * TableCellButton의 `tableCellMenu` prop으로 주입한다.\r\n */\r\nexport function LumirTableCellMenu<\r\n I extends InlineContentSchema = DefaultInlineContentSchema,\r\n S extends StyleSchema = DefaultStyleSchema,\r\n>(props: TableCellMenuProps<I, S> & { children?: ReactNode }) {\r\n const Components = useComponentsContext()!;\r\n\r\n return (\r\n <Components.Generic.Menu.Dropdown\r\n className={\"bn-menu-dropdown bn-drag-handle-menu\"}\r\n >\r\n {props.children || (\r\n <>\r\n <SplitButton\r\n block={props.block}\r\n rowIndex={props.rowIndex}\r\n colIndex={props.colIndex}\r\n />\r\n <LumirCellColorPickerButton\r\n block={props.block}\r\n rowIndex={props.rowIndex}\r\n colIndex={props.colIndex}\r\n />\r\n </>\r\n )}\r\n </Components.Generic.Menu.Dropdown>\r\n );\r\n}\r\n","\"use client\";\r\n\r\n/**\r\n * 블록 드래그 핸들(좌측 ⠿) 드롭다운 메뉴의 커스텀 버전.\r\n *\r\n * BlockNote 기본 DragHandleMenu(삭제/색/행 제목/열 제목)를 그대로 재현하고,\r\n * **표 블록일 때 \"표 정렬\"(좌/가운데/우) 항목을 추가**한다.\r\n * (표 정렬을 셀 grip 메뉴가 아니라 블록 메뉴로 노출하기 위함.)\r\n *\r\n * 기본 DragHandleMenu는 children을 주면 기본 항목 대신 children만 렌더하므로,\r\n * 기본 항목들을 명시적으로 재구성한 뒤 정렬 항목을 덧붙인다.\r\n * (TableRowHeaderItem/TableColumnHeaderItem은 비-표 블록에서 자동으로 null을 반환한다.)\r\n */\r\n\r\nimport {\r\n DragHandleMenu,\r\n RemoveBlockItem,\r\n BlockColorsItem,\r\n TableRowHeaderItem,\r\n TableColumnHeaderItem,\r\n useComponentsContext,\r\n useBlockNoteEditor,\r\n useDictionary,\r\n type DragHandleMenuProps,\r\n} from \"@blocknote/react\";\r\nimport type { TableAlignment } from \"../extensions/TableAlignmentExtension\";\r\nimport {\r\n getTableAlignment,\r\n setTableAlignment,\r\n} from \"../utils/prosemirror-table-utils\";\r\n\r\nconst TABLE_ALIGN_ICONS: Record<TableAlignment, JSX.Element> = {\r\n left: (\r\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.4\">\r\n <rect x=\"1.5\" y=\"4\" width=\"7\" height=\"8\" rx=\"1\" fill=\"currentColor\" stroke=\"none\" />\r\n <line x1=\"1.5\" y1=\"1.5\" x2=\"14.5\" y2=\"1.5\" />\r\n <line x1=\"1.5\" y1=\"14.5\" x2=\"14.5\" y2=\"14.5\" />\r\n </svg>\r\n ),\r\n center: (\r\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.4\">\r\n <rect x=\"4.5\" y=\"4\" width=\"7\" height=\"8\" rx=\"1\" fill=\"currentColor\" stroke=\"none\" />\r\n <line x1=\"1.5\" y1=\"1.5\" x2=\"14.5\" y2=\"1.5\" />\r\n <line x1=\"1.5\" y1=\"14.5\" x2=\"14.5\" y2=\"14.5\" />\r\n </svg>\r\n ),\r\n right: (\r\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.4\">\r\n <rect x=\"7.5\" y=\"4\" width=\"7\" height=\"8\" rx=\"1\" fill=\"currentColor\" stroke=\"none\" />\r\n <line x1=\"1.5\" y1=\"1.5\" x2=\"14.5\" y2=\"1.5\" />\r\n <line x1=\"1.5\" y1=\"14.5\" x2=\"14.5\" y2=\"14.5\" />\r\n </svg>\r\n ),\r\n};\r\n\r\nconst TABLE_ALIGN_LABELS: Record<TableAlignment, string> = {\r\n left: \"표 왼쪽 정렬\",\r\n center: \"표 가운데 정렬\",\r\n right: \"표 오른쪽 정렬\",\r\n};\r\n\r\n/** 표 블록일 때만 \"표 정렬\" 항목(좌/가운데/우)을 렌더. table 노드 attr(tableAlignment)에 적용. */\r\nfunction TableAlignmentItems(props: { block: any }) {\r\n const Components = useComponentsContext()!;\r\n const editor = useBlockNoteEditor();\r\n\r\n if (props.block?.type !== \"table\" || !props.block?.id) {\r\n return null;\r\n }\r\n\r\n const current = getTableAlignment(editor, props.block.id) as TableAlignment;\r\n\r\n return (\r\n <>\r\n {([\"left\", \"center\", \"right\"] as TableAlignment[]).map((align) => (\r\n <Components.Generic.Menu.Item\r\n key={`tableAlign-${align}`}\r\n icon={TABLE_ALIGN_ICONS[align]}\r\n checked={current === align}\r\n onClick={() => {\r\n setTableAlignment(editor, props.block.id, align);\r\n editor.setTextCursorPosition(props.block.id);\r\n }}\r\n >\r\n {TABLE_ALIGN_LABELS[align]}\r\n </Components.Generic.Menu.Item>\r\n ))}\r\n </>\r\n );\r\n}\r\n\r\nexport const LumirDragHandleMenu = (props: DragHandleMenuProps) => {\r\n const dict = useDictionary();\r\n\r\n return (\r\n <DragHandleMenu {...props}>\r\n <RemoveBlockItem {...props}>\r\n {dict.drag_handle.delete_menuitem}\r\n </RemoveBlockItem>\r\n <BlockColorsItem {...props}>\r\n {dict.drag_handle.colors_menuitem}\r\n </BlockColorsItem>\r\n <TableRowHeaderItem {...(props as any)}>\r\n {dict.drag_handle.header_row_menuitem}\r\n </TableRowHeaderItem>\r\n <TableColumnHeaderItem {...(props as any)}>\r\n {dict.drag_handle.header_column_menuitem}\r\n </TableColumnHeaderItem>\r\n <TableAlignmentItems block={props.block} />\r\n </DragHandleMenu>\r\n );\r\n};\r\n","\"use client\";\r\n\r\n/**\r\n * Notion 스타일 FOCUS 기반 테이블 grip 컨트롤러.\r\n *\r\n * 동작(Notion 레퍼런스):\r\n * 1) 셀 focus 시 → 그 셀 테두리에 하이라이트(focus 보더) 표시\r\n * 2) 셀 focus 시 → 상(열)·좌(행)·우(셀) 보더에 \"짧고 두꺼운 gutter 바\"만 표시\r\n * (발판 grip은 아직 노출하지 않음)\r\n * 3) gutter에 hover 시 → 그 자리에 발판 grip(드래그 점 / 드롭다운 화살표) 노출\r\n * 4) grip 클릭 시 → 드롭다운 메뉴\r\n *\r\n * BlockNoteView는 tableHandles={false}로 기본 hover 핸들을 끄고, 이 컨트롤러를\r\n * 자식으로 렌더한다. grip/메뉴/드래그는 BlockNote 공개 컴포넌트(TableHandle,\r\n * TableCellButton)와 코어 editor.tableHandles 메서드를 재사용한다.\r\n *\r\n * 핵심 사실:\r\n * - tableHandles={false}는 React 컨트롤러만 끄고 코어 플러그인은 유지된다 →\r\n * colDragStart/rowDragStart/dragEnd/메뉴 동작 재사용 가능.\r\n * - 메뉴 동작(add/remove)은 우리가 넘기는 index prop으로 동작하므로 안전하다.\r\n * - 드래그만 코어 hover 상태에 의존 → gutter pointer-enter 시 합성 mousemove로\r\n * 코어 인덱스를 포커스 셀과 동기화한다(TS-private view 미접근).\r\n * - grip을 클릭/드래그하면 ProseMirror 선택이 셀 밖으로 바뀌어 focused가 즉시\r\n * 해제될 수 있다 → 상호작용(pointerdown/메뉴/드래그) 동안 recompute를 freeze.\r\n */\r\n\r\nimport {\r\n BlockSchema,\r\n InlineContentSchema,\r\n StyleSchema,\r\n} from \"@blocknote/core\";\r\nimport {\r\n ExtendButton,\r\n TableCellButton,\r\n TableHandle,\r\n useBlockNoteEditor,\r\n useEditorContentOrSelectionChange,\r\n useExtendButtonsPositioning,\r\n useUIPluginState,\r\n} from \"@blocknote/react\";\r\nimport { autoUpdate, FloatingPortal } from \"@floating-ui/react\";\r\nimport type { DragEvent as ReactDragEvent } from \"react\";\r\nimport { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\r\nimport {\r\n useFocusedCellHandlePositioning,\r\n useTableCornerPositioning,\r\n} from \"./hooks/useFocusedCellHandlePositioning\";\r\nimport { LumirTableCellMenu } from \"./color/LumirColorControls\";\r\nimport {\r\n measureTableForScale,\r\n setTableScalePreview,\r\n commitTableScale,\r\n} from \"../extensions/tableScaling\";\r\nimport { findTableNodePos } from \"../utils/prosemirror-table-utils\";\r\nimport {\r\n TABLE_SCALE_MIN_COL_WIDTH,\r\n TABLE_SCALE_MAX,\r\n ROW_RESIZE_MIN_HEIGHT,\r\n} from \"../constants/limits\";\r\n\r\ntype FocusedCell = {\r\n block: any;\r\n rowIndex: number;\r\n colIndex: number;\r\n cellEl: HTMLElement;\r\n tbodyEl: HTMLElement; // 표(tbody) — gutter 표-가장자리 배치 + 열/행 하이라이트 범위용\r\n widgetContainer: HTMLElement;\r\n};\r\n\r\n/**\r\n * 포커스된 셀 중앙에 합성 mousemove를 디스패치해 코어 TableHandles 플러그인의\r\n * hover 상태(rowIndex/colIndex/referencePosCell/tablePos/...)를 포커스 셀과\r\n * 동기화한다. 코어 리스너(pmView.dom)가 직접 받아 \"cell\" 분기를 실행한다.\r\n * 마우스 버튼이 눌리지 않은 상태(pointer-enter)에서만 호출해야 한다.\r\n */\r\nfunction syncCoreHoverToFocusedCell(cellEl: HTMLElement) {\r\n const r = cellEl.getBoundingClientRect();\r\n cellEl.dispatchEvent(\r\n new MouseEvent(\"mousemove\", {\r\n bubbles: true,\r\n cancelable: true,\r\n view: window,\r\n clientX: r.left + r.width / 2,\r\n clientY: r.top + r.height / 2,\r\n }),\r\n );\r\n}\r\n\r\nexport function LumirTableHandlesController() {\r\n const editor = useBlockNoteEditor<\r\n BlockSchema,\r\n InlineContentSchema,\r\n StyleSchema\r\n >();\r\n\r\n const [focused, setFocused] = useState<FocusedCell | null>(null);\r\n const [menuContainerRef, setMenuContainerRef] =\r\n useState<HTMLDivElement | null>(null);\r\n // overlay 엘리먼트는 FloatingPortal로 한 틱 늦게 마운트되므로 state-backed ref로\r\n // 잡아, 실제 마운트 시점에 사이징 effect가 재실행되도록 한다.\r\n const [overlayEl, setOverlayEl] = useState<HTMLDivElement | null>(null);\r\n\r\n // 어떤 gutter의 메뉴가 열려 있는지(활성 파란 발판 + 열림 동안 발판 유지용).\r\n const [openMenu, setOpenMenu] = useState<\"col\" | \"row\" | \"cell\" | null>(null);\r\n\r\n // 상호작용 중에는 selection 변경으로 focused가 해제되지 않도록 recompute를 멈춘다.\r\n const frozenRef = useRef(false);\r\n const menuOpenRef = useRef(false);\r\n const draggingRef = useRef(false);\r\n\r\n // 그립 메뉴의 행/열 삭제는 코어 removeRowOrColumn(= view.tablePos/selection 의존)을\r\n // 쓰는데, grip 클릭으로 ProseMirror 선택이 표 밖으로 빠지면 그 경로가 실패한다(첫 열/행\r\n // 삭제 안 됨). 포커스된 표 id를 에디터에 노출해, LumirEditor의 삭제 래퍼가 선택/hover와\r\n // 무관하게 정확한 표를 찾도록 한다. focused는 상호작용 중 freeze되어 유지된다.\r\n useEffect(() => {\r\n (editor as any).__lumirActiveTableId = focused?.block?.id ?? null;\r\n }, [editor, focused]);\r\n\r\n const recompute = useCallback(() => {\r\n if (frozenRef.current) {\r\n return;\r\n }\r\n\r\n const th = editor.tableHandles;\r\n const view = editor.prosemirrorView;\r\n if (!th || !view) {\r\n setFocused(null);\r\n return;\r\n }\r\n\r\n let cellEl: HTMLElement | null = null;\r\n try {\r\n const { node } = view.domAtPos(view.state.selection.from);\r\n const el =\r\n node.nodeType === Node.TEXT_NODE\r\n ? (node.parentElement as HTMLElement | null)\r\n : (node as HTMLElement);\r\n cellEl = (el?.closest?.(\"td, th\") as HTMLElement | null) ?? null;\r\n } catch {\r\n cellEl = null;\r\n }\r\n if (!cellEl || !cellEl.isConnected) {\r\n setFocused(null);\r\n return;\r\n }\r\n\r\n const blockId = cellEl.closest(\"[data-id]\")?.getAttribute(\"data-id\");\r\n const block = blockId ? editor.getBlock(blockId) : undefined;\r\n if (!block || block.type !== \"table\") {\r\n setFocused(null);\r\n return;\r\n }\r\n\r\n const cellSel = th.getCellSelection();\r\n if (!cellSel) {\r\n setFocused(null);\r\n return;\r\n }\r\n\r\n const widgetContainer = cellEl\r\n .closest(\".tableWrapper\")\r\n ?.querySelector(\".table-widgets-container\") as HTMLElement | null;\r\n const tbodyEl = cellEl.closest(\"tbody\") as HTMLElement | null;\r\n if (!widgetContainer || !tbodyEl) {\r\n setFocused(null);\r\n return;\r\n }\r\n\r\n setFocused({\r\n block,\r\n rowIndex: cellSel.from.row,\r\n colIndex: cellSel.from.col,\r\n cellEl,\r\n tbodyEl,\r\n widgetContainer,\r\n });\r\n }, [editor]);\r\n\r\n useEditorContentOrSelectionChange(recompute, editor);\r\n useEffect(() => {\r\n recompute();\r\n }, [recompute]);\r\n\r\n // pointerdown으로 freeze한 상호작용이 메뉴/드래그로 이어지지 않은 경우의 안전장치.\r\n useEffect(() => {\r\n const onUp = () => {\r\n requestAnimationFrame(() => {\r\n if (!menuOpenRef.current && !draggingRef.current && frozenRef.current) {\r\n frozenRef.current = false;\r\n recompute();\r\n }\r\n });\r\n };\r\n window.addEventListener(\"pointerup\", onUp);\r\n return () => window.removeEventListener(\"pointerup\", onUp);\r\n }, [recompute]);\r\n\r\n // 활성 보더 오버레이 위치/크기 갱신(스크롤/리사이즈 자동 추적).\r\n // openMenu에 따라 범위가 달라진다: 열 메뉴→열 전체, 행 메뉴→행 전체, 그 외→셀.\r\n useEffect(() => {\r\n const f = focused;\r\n if (!f || !overlayEl) {\r\n return;\r\n }\r\n // 보더 두께(2px)의 절반 = PAD(1px). 정수/DPR 반올림으로 거터와 정렬 유지.\r\n const PAD = 1;\r\n const update = () => {\r\n const cr = f.cellEl.getBoundingClientRect();\r\n const tr = f.tbodyEl.getBoundingClientRect();\r\n const kr = f.widgetContainer.getBoundingClientRect();\r\n // 활성 보더 범위: 열 메뉴=열 전체(표 상하), 행 메뉴=행 전체(표 좌우), 그 외=셀\r\n const x1 = openMenu === \"row\" ? tr.left : cr.left;\r\n const y1 = openMenu === \"col\" ? tr.top : cr.top;\r\n const x2 = openMenu === \"row\" ? tr.right : cr.right;\r\n const y2 = openMenu === \"col\" ? tr.bottom : cr.bottom;\r\n const dpr = window.devicePixelRatio || 1;\r\n const rd = (v: number) => Math.round(v * dpr) / dpr;\r\n const left = rd(x1 - kr.left) - PAD;\r\n const top = rd(y1 - kr.top) - PAD;\r\n const right = rd(x2 - kr.left) + PAD;\r\n const bottom = rd(y2 - kr.top) + PAD;\r\n overlayEl.style.transform = `translate(${left}px, ${top}px)`;\r\n overlayEl.style.width = `${right - left}px`;\r\n overlayEl.style.height = `${bottom - top}px`;\r\n };\r\n update();\r\n const stopAutoUpdate = autoUpdate(f.cellEl, overlayEl, update);\r\n // 다른 행 높이 변경(행 리사이즈 등)으로 표가 reflow되면 focus 셀 자체는 크기가\r\n // 안 변해 autoUpdate가 발화하지 않는다. 특히 \"아래\" 행이 커지면 widgetContainer가\r\n // 내려가 오버레이가 셀 밖으로 드리프트한다 → tbody 크기를 직접 관찰해 재포지셔닝.\r\n const ro = new ResizeObserver(update);\r\n ro.observe(f.tbodyEl);\r\n return () => {\r\n stopAutoUpdate();\r\n ro.disconnect();\r\n };\r\n }, [focused, overlayEl, openMenu]);\r\n\r\n const cellEl = focused?.cellEl ?? null;\r\n const tbodyEl = focused?.tbodyEl ?? null;\r\n const show = focused !== null;\r\n const rowHandle = useFocusedCellHandlePositioning(cellEl, tbodyEl, \"row\", show);\r\n const colHandle = useFocusedCellHandlePositioning(cellEl, tbodyEl, \"col\", show);\r\n const cellHandle = useFocusedCellHandlePositioning(\r\n cellEl,\r\n tbodyEl,\r\n \"cell\",\r\n show,\r\n );\r\n const th = editor.tableHandles;\r\n\r\n // 표 우측/하단 hover 시 \"행/열 추가\" ExtendButton — 코어 hover 상태 기반(focus와 독립).\r\n const coreState = useUIPluginState(\r\n editor.tableHandles!.onUpdate.bind(editor.tableHandles!),\r\n );\r\n const { addOrRemoveColumnsButton, addOrRemoveRowsButton } =\r\n useExtendButtonsPositioning(\r\n coreState?.showAddOrRemoveColumnsButton || false,\r\n coreState?.showAddOrRemoveRowsButton || false,\r\n coreState?.referencePosTable || null,\r\n );\r\n const onStartExtend = useCallback(() => {\r\n editor.tableHandles?.freezeHandles();\r\n }, [editor]);\r\n const onEndExtend = useCallback(() => {\r\n editor.tableHandles?.unfreezeHandles();\r\n }, [editor]);\r\n\r\n // 표 우하단 코너 스케일 hit-zone(focus 독립, 코어 hover 기반 → 셀 클릭 없이도 노출).\r\n const tableCorner = useTableCornerPositioning(\r\n coreState?.referencePosTable ?? null,\r\n !!coreState?.widgetContainer,\r\n );\r\n\r\n // 메뉴 열림/닫힘 핸들러(컨트롤별). 열림 동안 frozen 유지 + openMenu로 활성 표시.\r\n const menuHandlers = useMemo(() => {\r\n const mk = (kind: \"col\" | \"row\" | \"cell\") => ({\r\n freeze: () => {\r\n menuOpenRef.current = true;\r\n frozenRef.current = true;\r\n setOpenMenu(kind);\r\n editor.tableHandles?.freezeHandles();\r\n },\r\n unfreeze: () => {\r\n menuOpenRef.current = false;\r\n frozenRef.current = false;\r\n setOpenMenu(null);\r\n editor.tableHandles?.unfreezeHandles();\r\n recompute();\r\n },\r\n });\r\n return { col: mk(\"col\"), row: mk(\"row\"), cell: mk(\"cell\") };\r\n }, [editor, recompute]);\r\n\r\n const onGutterPointerDown = useCallback(() => {\r\n frozenRef.current = true;\r\n }, []);\r\n\r\n const onGutterPointerEnter = useCallback(\r\n (e: { buttons: number }) => {\r\n if (e.buttons === 0 && focused) {\r\n syncCoreHoverToFocusedCell(focused.cellEl);\r\n }\r\n },\r\n [focused],\r\n );\r\n\r\n const makeDragStart = useCallback(\r\n (dir: \"row\" | \"col\") => (e: ReactDragEvent) => {\r\n draggingRef.current = true;\r\n frozenRef.current = true;\r\n if (dir === \"row\") {\r\n editor.tableHandles?.rowDragStart(e as unknown as DragEvent);\r\n } else {\r\n editor.tableHandles?.colDragStart(e as unknown as DragEvent);\r\n }\r\n },\r\n [editor],\r\n );\r\n\r\n const onDragEnd = useCallback(() => {\r\n editor.tableHandles?.dragEnd();\r\n draggingRef.current = false;\r\n frozenRef.current = false;\r\n recompute();\r\n }, [editor, recompute]);\r\n\r\n const noop = useCallback(() => {}, []);\r\n\r\n // 우하단 코너 드래그 → 표 전체 종횡비 고정 스케일.\r\n // hit-zone은 에디터 DOM 밖(오버레이)이라 row/column 리사이즈와 충돌하지 않는다.\r\n // 대상 표는 코어 hover 상태(coreState.block) → 셀 focus 없이도 동작.\r\n // 드래그 동안 freezeHandles로 코어 hover 상태를 고정(마우스가 표를 벗어나도 유지).\r\n const onScaleStart = useCallback(\r\n (e: React.PointerEvent) => {\r\n e.preventDefault();\r\n e.stopPropagation();\r\n const view = (editor as any).prosemirrorView;\r\n const blockId = coreState?.block?.id;\r\n if (!view || !blockId) return;\r\n const tablePos = findTableNodePos((editor as any)._tiptapEditor, blockId);\r\n if (tablePos < 0) return;\r\n const base = measureTableForScale(view, tablePos);\r\n if (!base) return;\r\n\r\n const minScale = Math.max(\r\n TABLE_SCALE_MIN_COL_WIDTH / Math.max(1, Math.min(...base.colWidths)),\r\n ROW_RESIZE_MIN_HEIGHT / Math.max(1, Math.min(...base.rowHeights)),\r\n );\r\n const startX = e.clientX;\r\n const startY = e.clientY;\r\n let current = base;\r\n editor.tableHandles?.freezeHandles();\r\n\r\n const onMove = (me: PointerEvent) => {\r\n if (me.buttons === 0) return onUp();\r\n const newW = base.origW + (me.clientX - startX);\r\n const newH = base.origH + (me.clientY - startY);\r\n const scale = Math.min(\r\n TABLE_SCALE_MAX,\r\n Math.max(minScale, Math.max(newW / base.origW, newH / base.origH)),\r\n );\r\n current = { ...base, scale };\r\n setTableScalePreview(view, current);\r\n };\r\n const onUp = () => {\r\n window.removeEventListener(\"pointermove\", onMove);\r\n window.removeEventListener(\"pointerup\", onUp);\r\n // ⚠️ rowResizing 교훈: 프리뷰 제거 후 커밋(스타일 충돌 방지).\r\n setTableScalePreview(view, null);\r\n commitTableScale(view, current);\r\n editor.tableHandles?.unfreezeHandles();\r\n };\r\n window.addEventListener(\"pointermove\", onMove);\r\n window.addEventListener(\"pointerup\", onUp);\r\n },\r\n [editor, coreState],\r\n );\r\n\r\n return (\r\n <>\r\n {/* 메뉴 컨테이너: grip 메뉴가 createPortal로 그려지는 대상. portal 밖에 둔다. */}\r\n <div ref={setMenuContainerRef} />\r\n {th && focused && menuContainerRef && (\r\n <FloatingPortal root={focused.widgetContainer}>\r\n {/* focus 보더 오버레이 (셀을 덮는 비클릭 테두리) */}\r\n <div ref={setOverlayEl} className=\"lumir-tbl-cell-focus\" />\r\n\r\n {/* 상(top) gutter → 열(column) */}\r\n {colHandle.isMounted && (\r\n <div\r\n ref={colHandle.ref}\r\n style={colHandle.style}\r\n className={\r\n \"lumir-tbl-gutter-wrap lumir-tbl-gutter-wrap--col\" +\r\n (openMenu === \"col\" ? \" lumir-tbl-gutter-wrap--active\" : \"\")\r\n }\r\n onPointerEnter={onGutterPointerEnter}\r\n onPointerDown={onGutterPointerDown}\r\n >\r\n <span className=\"lumir-tbl-gutter\" />\r\n <div className=\"lumir-tbl-grip\">\r\n <TableHandle\r\n editor={editor as any}\r\n orientation=\"column\"\r\n index={focused.colIndex}\r\n block={focused.block}\r\n dragStart={makeDragStart(\"col\")}\r\n dragEnd={onDragEnd}\r\n freezeHandles={menuHandlers.col.freeze}\r\n unfreezeHandles={menuHandlers.col.unfreeze}\r\n menuContainer={menuContainerRef}\r\n showOtherSide={noop}\r\n hideOtherSide={noop}\r\n />\r\n </div>\r\n </div>\r\n )}\r\n\r\n {/* 좌(left) gutter → 행(row) */}\r\n {rowHandle.isMounted && (\r\n <div\r\n ref={rowHandle.ref}\r\n style={rowHandle.style}\r\n className={\r\n \"lumir-tbl-gutter-wrap lumir-tbl-gutter-wrap--row\" +\r\n (openMenu === \"row\" ? \" lumir-tbl-gutter-wrap--active\" : \"\")\r\n }\r\n onPointerEnter={onGutterPointerEnter}\r\n onPointerDown={onGutterPointerDown}\r\n >\r\n <span className=\"lumir-tbl-gutter\" />\r\n <div className=\"lumir-tbl-grip\">\r\n <TableHandle\r\n editor={editor as any}\r\n orientation=\"row\"\r\n index={focused.rowIndex}\r\n block={focused.block}\r\n dragStart={makeDragStart(\"row\")}\r\n dragEnd={onDragEnd}\r\n freezeHandles={menuHandlers.row.freeze}\r\n unfreezeHandles={menuHandlers.row.unfreeze}\r\n menuContainer={menuContainerRef}\r\n showOtherSide={noop}\r\n hideOtherSide={noop}\r\n />\r\n </div>\r\n </div>\r\n )}\r\n\r\n {/* 우(right) → 파란 선택 앵커(rest) / hover 시 셀(색상·분할) 메뉴.\r\n 행/열 grip 메뉴가 열려 범위 하이라이트 중에는 노출하지 않는다. */}\r\n {cellHandle.isMounted && openMenu !== \"col\" && openMenu !== \"row\" && (\r\n <div\r\n ref={cellHandle.ref}\r\n style={cellHandle.style}\r\n className={\r\n \"lumir-tbl-gutter-wrap lumir-tbl-gutter-wrap--cell\" +\r\n (openMenu === \"cell\" ? \" lumir-tbl-gutter-wrap--active\" : \"\")\r\n }\r\n onPointerDown={onGutterPointerDown}\r\n >\r\n <span className=\"lumir-tbl-gutter\" />\r\n <div className=\"lumir-tbl-grip\">\r\n <TableCellButton\r\n editor={editor as any}\r\n rowIndex={focused.rowIndex}\r\n colIndex={focused.colIndex}\r\n block={focused.block}\r\n tableCellMenu={LumirTableCellMenu as any}\r\n menuContainer={menuContainerRef}\r\n freezeHandles={menuHandlers.cell.freeze}\r\n unfreezeHandles={menuHandlers.cell.unfreeze}\r\n />\r\n </div>\r\n </div>\r\n )}\r\n </FloatingPortal>\r\n )}\r\n\r\n {/* 표 우측/하단 hover 시 행/열 추가(ExtendButton). focus와 무관하게 코어 hover 상태로 표시. */}\r\n {th && coreState?.widgetContainer && (\r\n <FloatingPortal root={coreState.widgetContainer}>\r\n <div ref={addOrRemoveRowsButton.ref} style={addOrRemoveRowsButton.style}>\r\n <ExtendButton\r\n editor={editor as any}\r\n orientation=\"addOrRemoveRows\"\r\n block={coreState.block}\r\n onMouseDown={onStartExtend}\r\n onMouseUp={onEndExtend}\r\n />\r\n </div>\r\n <div\r\n ref={addOrRemoveColumnsButton.ref}\r\n style={addOrRemoveColumnsButton.style}\r\n >\r\n <ExtendButton\r\n editor={editor as any}\r\n orientation=\"addOrRemoveColumns\"\r\n block={coreState.block}\r\n onMouseDown={onStartExtend}\r\n onMouseUp={onEndExtend}\r\n />\r\n </div>\r\n\r\n {/* 우하단 코너 → 표 전체 종횡비 고정 스케일(투명 hit-zone, focus 독립).\r\n hover된 표 기준이라 셀 클릭 없이도 동작. */}\r\n {tableCorner.isMounted && (\r\n <div\r\n ref={tableCorner.ref}\r\n style={tableCorner.style}\r\n className=\"lumir-tbl-scale-handle\"\r\n title=\"표 크기 조절 (종횡비 고정)\"\r\n onPointerDown={onScaleStart}\r\n />\r\n )}\r\n </FloatingPortal>\r\n )}\r\n </>\r\n );\r\n}\r\n","import {\r\n autoUpdate,\r\n offset,\r\n useFloating,\r\n useTransitionStyles,\r\n} from \"@floating-ui/react\";\r\nimport type { CSSProperties } from \"react\";\r\nimport { useEffect, useMemo } from \"react\";\r\n\r\n/**\r\n * Notion 스타일 gutter/grip 배치용 positioner (표 가장자리 기준).\r\n *\r\n * Notion 레퍼런스: 열 gutter는 표 **상단** 가장자리(focus 열 중앙), 행 gutter는 표\r\n * **좌측** 가장자리(focus 행 중앙), 셀 핸들은 셀 **우상단 모서리**에 놓인다.\r\n * 따라서 셀이 아니라 표(tbody) 가장자리를 기준으로 배치해야 하므로, floating-ui\r\n * **virtual element**(`{ getBoundingClientRect, contextElement }`)를 reference로 쓴다.\r\n *\r\n * - \"col\" → 표 상단 + 열 중앙 (zero-height ref, placement \"top\")\r\n * - \"row\" → 표 좌측 + 행 중앙 (zero-width ref, placement \"left\")\r\n * - \"cell\" → 셀 우측 보더 (cell rect, placement \"right\")\r\n *\r\n * `contextElement: cellEl`을 넣어야 `autoUpdate`가 관찰할 실제 엘리먼트를 가짐\r\n * (스크롤/리사이즈 시 gutter가 가장자리에 고정 유지됨).\r\n */\r\nexport function useFocusedCellHandlePositioning(\r\n cellEl: HTMLElement | null,\r\n tbodyEl: HTMLElement | null,\r\n orientation: \"row\" | \"col\" | \"cell\" | \"corner\",\r\n show: boolean,\r\n): {\r\n isMounted: boolean;\r\n ref: (node: HTMLElement | null) => void;\r\n style: CSSProperties;\r\n} {\r\n const { refs, floatingStyles, context, update } = useFloating({\r\n open: show,\r\n placement:\r\n orientation === \"row\"\r\n ? \"left\"\r\n : orientation === \"col\"\r\n ? \"top\"\r\n : orientation === \"corner\"\r\n ? \"bottom-start\"\r\n : \"right\",\r\n // col/row/cell: 가장자리에 14px hit-area 중앙 정렬(-7).\r\n // corner: 18px hit-zone이 표 우하단 모서리에 걸치도록 위/좌로 살짝 당김(모서리 hover 자연).\r\n middleware: [\r\n offset(\r\n orientation === \"corner\" ? { mainAxis: -6, crossAxis: -6 } : -7,\r\n ),\r\n ],\r\n whileElementsMounted: autoUpdate,\r\n });\r\n\r\n const { isMounted, styles } = useTransitionStyles(context);\r\n\r\n // 다른 행 높이 변경으로 표가 reflow되면(특히 focus 셀보다 아래 행이 커지면) focus 셀\r\n // 자체는 크기가 안 변해 autoUpdate가 발화하지 않아 gutter/grip이 어긋난다.\r\n // tbody 크기를 직접 관찰해 재포지셔닝한다.\r\n useEffect(() => {\r\n if (!show || !tbodyEl) {\r\n return;\r\n }\r\n const ro = new ResizeObserver(() => update());\r\n ro.observe(tbodyEl);\r\n return () => ro.disconnect();\r\n }, [show, tbodyEl, update]);\r\n\r\n useEffect(() => {\r\n if (!cellEl) {\r\n refs.setReference(null);\r\n return;\r\n }\r\n refs.setReference({\r\n contextElement: cellEl,\r\n getBoundingClientRect: () => {\r\n const c = cellEl.getBoundingClientRect();\r\n const t = tbodyEl?.getBoundingClientRect() ?? c;\r\n if (orientation === \"col\") {\r\n // 표 상단 가장자리 + focus 셀의 열 폭(zero-height)\r\n return new DOMRect(c.left, t.top, c.width, 0);\r\n }\r\n if (orientation === \"row\") {\r\n // 표 좌측 가장자리 + focus 셀의 행 높이(zero-width)\r\n return new DOMRect(t.left, c.top, 0, c.height);\r\n }\r\n if (orientation === \"corner\") {\r\n // 표 우하단 모서리의 영점(zero-size) → placement \"bottom-start\"로 핸들 좌상단을\r\n // 모서리에 맞닿게(핸들이 모서리에서 우·하로 뻗음).\r\n return new DOMRect(t.right, t.bottom, 0, 0);\r\n }\r\n // 셀 핸들: 셀 rect (placement \"right\" → 우측 보더)\r\n return c;\r\n },\r\n });\r\n }, [cellEl, tbodyEl, orientation, refs]);\r\n\r\n return useMemo(\r\n () => ({\r\n isMounted,\r\n ref: refs.setFloating,\r\n // display는 CSS 클래스에서 제어한다(absolute 자식 stacking).\r\n style: {\r\n ...styles,\r\n ...floatingStyles,\r\n },\r\n }),\r\n [floatingStyles, isMounted, refs.setFloating, styles],\r\n );\r\n}\r\n\r\n/**\r\n * 표 우하단 코너 스케일 hit-zone 배치(focus 독립, 코어 hover 상태 기반).\r\n *\r\n * focus가 아니라 `coreState.referencePosTable`(hover된 표의 DOMRect)을 기준으로 잡으므로\r\n * 셀을 클릭하지 않아도 표 hover만으로 코너 hit-zone이 노출된다(ExtendButton과 동일 패턴).\r\n * 코너에 살짝 안쪽으로 걸치도록 배치해, hover 시 마우스가 표 위에 남아 coreState가 유지된다.\r\n */\r\nexport function useTableCornerPositioning(\r\n referencePosTable: DOMRect | null,\r\n show: boolean,\r\n): {\r\n isMounted: boolean;\r\n ref: (node: HTMLElement | null) => void;\r\n style: CSSProperties;\r\n} {\r\n const { refs, floatingStyles, context } = useFloating({\r\n open: show,\r\n placement: \"bottom-start\",\r\n // 18px hit-zone을 모서리에서 안쪽(위/좌)으로 당겨 표 위에 걸치게 한다.\r\n middleware: [offset({ mainAxis: -12, crossAxis: -12 })],\r\n whileElementsMounted: autoUpdate,\r\n });\r\n const { isMounted, styles } = useTransitionStyles(context);\r\n\r\n useEffect(() => {\r\n if (!referencePosTable) {\r\n refs.setReference(null);\r\n return;\r\n }\r\n refs.setReference({\r\n getBoundingClientRect: () =>\r\n new DOMRect(referencePosTable.right, referencePosTable.bottom, 0, 0),\r\n });\r\n }, [referencePosTable, refs]);\r\n\r\n return useMemo(\r\n () => ({\r\n isMounted,\r\n ref: refs.setFloating,\r\n style: { ...styles, ...floatingStyles },\r\n }),\r\n [floatingStyles, isMounted, refs.setFloating, styles],\r\n );\r\n}\r\n","import type { DefaultPartialBlock } from \"../types\";\r\n\r\n/**\r\n * BlockNote가 모델링하지 않는 표 셀 속성(verticalAlignment, rowHeight 등)을 블록 JSON에\r\n * 주입하는 직렬화 헬퍼.\r\n *\r\n * BlockNote의 nodeToBlock(contentNodeToTableContent)은 고정된 prop 집합\r\n * (colspan/rowspan/backgroundColor/textColor/textAlignment)만 cell.props에 담으므로,\r\n * 커스텀 글로벌 attribute는 블록 JSON에서 누락된다. 이 모듈은 ProseMirror 상태에서\r\n * 직접 읽어 cell.props에 다시 주입한다(onContentChange 직전).\r\n *\r\n * 로드(역방향)는 별도 코드가 필요 없다: BlockNote blockToNode가 `...cell.props`를 노드\r\n * attr로 그대로 펼치므로, 글로벌 attribute로 등록만 돼 있으면 자동 복원된다.\r\n */\r\n\r\n/**\r\n * 각 셀 속성의 \"기본값\"(= 주입에서 제외할 값). 기본값이거나 null/undefined면 주입하지 않아\r\n * 블록 JSON을 불필요하게 키우지 않는다.\r\n */\r\nconst CELL_ATTR_DEFAULTS: Record<string, unknown> = {\r\n verticalAlignment: \"top\",\r\n rowHeight: null,\r\n};\r\n\r\nfunction isMeaningful(attr: string, value: unknown): boolean {\r\n if (value === null || value === undefined) {\r\n return false;\r\n }\r\n return value !== CELL_ATTR_DEFAULTS[attr];\r\n}\r\n\r\ntype CellAttrs = {\r\n row: number;\r\n col: number;\r\n props: Record<string, unknown>;\r\n};\r\n\r\n/**\r\n * ProseMirror 문서에서 각 table 블록 id → 셀별(주입 대상 attr만 담은) props 맵을 구축한다.\r\n */\r\nfunction buildTableCellAttrMap(\r\n doc: any,\r\n attrs: string[],\r\n): Map<string, CellAttrs[]> {\r\n const result = new Map<string, CellAttrs[]>();\r\n\r\n doc.descendants((node: any) => {\r\n if (node.type.name === \"blockContainer\") {\r\n const blockId = node.attrs?.id;\r\n const contentNode = node.firstChild;\r\n if (blockId && contentNode?.type.name === \"table\") {\r\n const cells: CellAttrs[] = [];\r\n let rowIndex = 0;\r\n contentNode.forEach((rowNode: any) => {\r\n if (rowNode.type.name === \"tableRow\") {\r\n let colIndex = 0;\r\n rowNode.forEach((cellNode: any) => {\r\n const props: Record<string, unknown> = {};\r\n for (const attr of attrs) {\r\n const value = cellNode.attrs?.[attr];\r\n if (isMeaningful(attr, value)) {\r\n props[attr] = value;\r\n }\r\n }\r\n if (Object.keys(props).length > 0) {\r\n cells.push({ row: rowIndex, col: colIndex, props });\r\n }\r\n colIndex++;\r\n });\r\n rowIndex++;\r\n }\r\n });\r\n if (cells.length > 0) {\r\n result.set(blockId, cells);\r\n }\r\n }\r\n return false;\r\n }\r\n });\r\n\r\n return result;\r\n}\r\n\r\nfunction patchBlocks(\r\n blocks: DefaultPartialBlock[],\r\n cellAttrMap: Map<string, CellAttrs[]>,\r\n): DefaultPartialBlock[] {\r\n return blocks.map((block) => {\r\n if (block.type !== \"table\" || !block.id || !block.content) {\r\n if (block.children && block.children.length > 0) {\r\n return {\r\n ...block,\r\n children: patchBlocks(\r\n block.children as DefaultPartialBlock[],\r\n cellAttrMap,\r\n ),\r\n };\r\n }\r\n return block;\r\n }\r\n\r\n const cells = cellAttrMap.get(block.id);\r\n if (!cells || cells.length === 0) {\r\n return block;\r\n }\r\n\r\n const content = block.content as any;\r\n if (content.type !== \"tableContent\" || !content.rows) {\r\n return block;\r\n }\r\n\r\n const newRows = content.rows.map((row: any, rowIndex: number) => {\r\n const newCells = row.cells.map((cell: any, colIndex: number) => {\r\n const match = cells.find(\r\n (c) => c.row === rowIndex && c.col === colIndex,\r\n );\r\n if (!match) {\r\n return cell;\r\n }\r\n if (cell && typeof cell === \"object\" && cell.type === \"tableCell\") {\r\n return {\r\n ...cell,\r\n props: { ...cell.props, ...match.props },\r\n };\r\n }\r\n return cell;\r\n });\r\n return { ...row, cells: newCells };\r\n });\r\n\r\n return {\r\n ...block,\r\n content: { ...content, rows: newRows },\r\n };\r\n });\r\n}\r\n\r\n/**\r\n * blocks 배열의 table 블록을 찾아, ProseMirror 상태에서 읽은 셀별 attr(`attrs`로 지정)을\r\n * cell.props에 주입한다. 한 번의 문서 순회로 여러 attr을 동시에 처리한다.\r\n *\r\n * @param attrs 주입할 셀 노드 attr 이름들(예: [\"verticalAlignment\", \"rowHeight\"])\r\n */\r\nexport function injectTableCellAttrs(\r\n blocks: DefaultPartialBlock[],\r\n editor: any,\r\n attrs: string[],\r\n): DefaultPartialBlock[] {\r\n const tiptap = editor?._tiptapEditor;\r\n if (!tiptap) {\r\n return blocks;\r\n }\r\n\r\n const { doc } = tiptap.state;\r\n const cellAttrMap = buildTableCellAttrMap(doc, attrs);\r\n if (cellAttrMap.size === 0) {\r\n return blocks;\r\n }\r\n\r\n return patchBlocks(blocks, cellAttrMap);\r\n}\r\n\r\n/**\r\n * verticalAlignment만 주입하는 하위호환 래퍼. 기존 호출부 호환을 위해 유지.\r\n * @deprecated 신규 호출부는 injectTableCellAttrs를 직접 사용.\r\n */\r\nexport function injectVerticalAlignment(\r\n blocks: DefaultPartialBlock[],\r\n editor: any,\r\n): DefaultPartialBlock[] {\r\n return injectTableCellAttrs(blocks, editor, [\"verticalAlignment\"]);\r\n}\r\n\r\n/** table 노드(블록 레벨) 보존 대상 attr과 기본값. */\r\nconst TABLE_BLOCK_ATTR_DEFAULTS: Record<string, unknown> = {\r\n tableAlignment: \"left\",\r\n};\r\n\r\n/**\r\n * ProseMirror 문서에서 table 블록 id → 블록 레벨 attr(tableAlignment 등) 맵을 만든다.\r\n * 셀이 아니라 table 노드 자체의 attr을 읽는다.\r\n */\r\nfunction buildTableBlockAttrMap(\r\n doc: any,\r\n attrs: string[],\r\n): Map<string, Record<string, unknown>> {\r\n const result = new Map<string, Record<string, unknown>>();\r\n\r\n doc.descendants((node: any) => {\r\n if (node.type.name === \"blockContainer\") {\r\n const blockId = node.attrs?.id;\r\n const contentNode = node.firstChild;\r\n if (blockId && contentNode?.type.name === \"table\") {\r\n const props: Record<string, unknown> = {};\r\n for (const attr of attrs) {\r\n const value = contentNode.attrs?.[attr];\r\n if (\r\n value !== null &&\r\n value !== undefined &&\r\n value !== TABLE_BLOCK_ATTR_DEFAULTS[attr]\r\n ) {\r\n props[attr] = value;\r\n }\r\n }\r\n if (Object.keys(props).length > 0) {\r\n result.set(blockId, props);\r\n }\r\n }\r\n return false;\r\n }\r\n return undefined;\r\n });\r\n\r\n return result;\r\n}\r\n\r\nfunction patchTableBlockProps(\r\n blocks: DefaultPartialBlock[],\r\n attrMap: Map<string, Record<string, unknown>>,\r\n): DefaultPartialBlock[] {\r\n return blocks.map((block) => {\r\n if (block.type === \"table\" && block.id && attrMap.has(block.id)) {\r\n return {\r\n ...block,\r\n props: { ...(block.props as any), ...attrMap.get(block.id) },\r\n } as DefaultPartialBlock;\r\n }\r\n if (block.children && block.children.length > 0) {\r\n return {\r\n ...block,\r\n children: patchTableBlockProps(\r\n block.children as DefaultPartialBlock[],\r\n attrMap,\r\n ),\r\n };\r\n }\r\n return block;\r\n });\r\n}\r\n\r\n/**\r\n * blocks 배열의 table 블록에, ProseMirror 상태에서 읽은 table 노드 블록 레벨 attr\r\n * (예: tableAlignment)을 block.props에 주입한다. BlockNote nodeToBlock이 table 노드의\r\n * 커스텀 attr을 block.props로 추출하지 않으므로 직접 주입한다.\r\n */\r\nexport function injectTableBlockAttrs(\r\n blocks: DefaultPartialBlock[],\r\n editor: any,\r\n): DefaultPartialBlock[] {\r\n const tiptap = editor?._tiptapEditor;\r\n if (!tiptap) {\r\n return blocks;\r\n }\r\n const { doc } = tiptap.state;\r\n const attrMap = buildTableBlockAttrMap(doc, [\"tableAlignment\"]);\r\n if (attrMap.size === 0) {\r\n return blocks;\r\n }\r\n return patchTableBlockProps(blocks, attrMap);\r\n}\r\n","import type { DefaultPartialBlock } from \"../types\";\r\n\r\n/**\r\n * 글자 크기(fontSize) 직렬화 호환 레이어.\r\n *\r\n * 배경:\r\n * - BlockNote는 인라인 `styles` 맵에 스키마에 없는 키가 있으면\r\n * `style ${name} not found in styleSchema` 예외를 던진다(blockToNode →\r\n * styledTextToNodes). 이미 배포된 구버전 SDK(≤0.4.15, fontSize 스펙 없음)가\r\n * `styles.fontSize`가 포함된 JSON을 initialContent로 받으면 에디터 생성이\r\n * 실패하며 React 트리가 크래시한다.\r\n * - 반면 styled-text 객체의 **형제(sibling) 키**는 BlockNote가 읽지 않으므로\r\n * 조용히 무시된다(크래시 없음).\r\n *\r\n * 전략:\r\n * - 저장/전달용 JSON(onContentChange 출력)에서는 `styles.fontSize`를 형제 키\r\n * `fontSize`로 내린다(lowerFontSize).\r\n * - 로드 시(initialContent)에는 형제 키를 다시 `styles.fontSize`로 올려\r\n * 신버전 에디터가 렌더링하게 한다(liftFontSize).\r\n *\r\n * ⚠️ 불변 규칙: 에디터 외부로 블록 JSON을 내보내는 모든 경로는 반드시\r\n * lowerFontSize를 거쳐야 한다(현재 출력 표면은 onContentChange 유일).\r\n * styles.fontSize가 저장 JSON에 유출되면 구버전 소비 앱이 크래시한다.\r\n */\r\n\r\n/**\r\n * 직렬화(저장) 포맷의 styled-text 항목.\r\n * fontSize는 styles 맵이 아닌 형제 키로 존재한다 — 구버전 SDK(≤0.4.15)는\r\n * styles만 순회하므로 이 키를 조용히 무시한다(파싱 오류 없음).\r\n */\r\nexport type SerializedStyledText = {\r\n type: \"text\";\r\n text: string;\r\n styles: Record<string, unknown>;\r\n /** 인라인 글자 크기(예: \"18px\"). 구버전 SDK에서는 무시됨 */\r\n fontSize?: string;\r\n};\r\n\r\ntype Dir = \"lower\" | \"lift\";\r\n\r\n/**\r\n * 배열 map 후 모든 요소가 원본과 동일하면 원본 배열 참조를 그대로 반환\r\n * (불필요한 재생성 방지 — table-vertical-alignment.ts의 불변 패턴 준수)\r\n */\r\nfunction mapPreservingRef<T>(arr: T[], fn: (item: T) => T): T[] {\r\n let changed = false;\r\n const next = arr.map((item) => {\r\n const mapped = fn(item);\r\n if (mapped !== item) changed = true;\r\n return mapped;\r\n });\r\n return changed ? next : arr;\r\n}\r\n\r\n/** styled-text 한 항목의 fontSize를 styles ↔ 형제 키 간 이동 */\r\nfunction mapStyledItem(item: any, dir: Dir): any {\r\n if (!item || typeof item !== \"object\" || item.type !== \"text\") {\r\n return item;\r\n }\r\n\r\n if (dir === \"lower\") {\r\n const fs = item.styles?.fontSize;\r\n if (fs == null) return item;\r\n const { fontSize: _removed, ...restStyles } = item.styles;\r\n return { ...item, styles: restStyles, fontSize: fs };\r\n }\r\n\r\n // lift: 형제 키 → styles.fontSize (둘 다 있으면 형제 키 우선)\r\n const fs = item.fontSize;\r\n if (fs == null) return item;\r\n const { fontSize: _lifted, ...rest } = item;\r\n return { ...rest, styles: { ...(item.styles ?? {}), fontSize: fs } };\r\n}\r\n\r\n/** 인라인 콘텐츠 항목 처리 — link는 중첩 content(StyledText[])를 재귀 */\r\nfunction mapInlineItem(node: any, dir: Dir): any {\r\n if (!node || typeof node !== \"object\") return node;\r\n if (node.type === \"link\" && Array.isArray(node.content)) {\r\n const content = mapPreservingRef(node.content, (n) =>\r\n mapStyledItem(n, dir),\r\n );\r\n return content === node.content ? node : { ...node, content };\r\n }\r\n return mapStyledItem(node, dir);\r\n}\r\n\r\n/** 테이블 셀 — `{type:\"tableCell\", content}` 객체와 평문 인라인 배열 두 형태 모두 처리 */\r\nfunction mapCell(cell: any, dir: Dir): any {\r\n if (cell && typeof cell === \"object\" && cell.type === \"tableCell\") {\r\n if (!Array.isArray(cell.content)) return cell;\r\n const content = mapPreservingRef(cell.content, (n) =>\r\n mapInlineItem(n, dir),\r\n );\r\n return content === cell.content ? cell : { ...cell, content };\r\n }\r\n if (Array.isArray(cell)) {\r\n return mapPreservingRef(cell, (n) => mapInlineItem(n, dir));\r\n }\r\n return cell;\r\n}\r\n\r\nfunction mapBlock(block: any, dir: Dir): any {\r\n if (!block || typeof block !== \"object\") return block;\r\n\r\n let next = block;\r\n const content = block.content;\r\n\r\n if (Array.isArray(content)) {\r\n // paragraph/heading/list 계열 인라인 콘텐츠\r\n const mapped = mapPreservingRef(content, (n) => mapInlineItem(n, dir));\r\n if (mapped !== content) next = { ...next, content: mapped };\r\n } else if (\r\n content &&\r\n typeof content === \"object\" &&\r\n content.type === \"tableContent\" &&\r\n Array.isArray(content.rows)\r\n ) {\r\n const rows = mapPreservingRef(content.rows, (row: any) => {\r\n if (!row || !Array.isArray(row.cells)) return row;\r\n const cells = mapPreservingRef(row.cells, (cell: any) =>\r\n mapCell(cell, dir),\r\n );\r\n return cells === row.cells ? row : { ...row, cells };\r\n });\r\n if (rows !== content.rows) next = { ...next, content: { ...content, rows } };\r\n }\r\n // content === undefined (image/video/htmlPreview/linkPreview) → 그대로 통과\r\n\r\n if (Array.isArray(block.children) && block.children.length > 0) {\r\n const children = mapPreservingRef(block.children, (b: any) =>\r\n mapBlock(b, dir),\r\n );\r\n if (children !== block.children) next = { ...next, children };\r\n }\r\n\r\n return next;\r\n}\r\n\r\n/**\r\n * 출력 방향: `styles.fontSize` → 형제 키 `fontSize`.\r\n * onContentChange로 내보내는 블록 JSON에 적용해 구버전 SDK 크래시를 방지한다.\r\n * 입력을 변형하지 않는다(불변).\r\n */\r\nexport function lowerFontSize(\r\n blocks: DefaultPartialBlock[],\r\n): DefaultPartialBlock[] {\r\n return mapPreservingRef(blocks as any[], (b) => mapBlock(b, \"lower\"));\r\n}\r\n\r\n/**\r\n * 입력 방향: 형제 키 `fontSize` → `styles.fontSize`.\r\n * initialContent를 에디터에 넘기기 전에 적용해 글자 크기를 복원한다.\r\n * 입력을 변형하지 않는다(불변).\r\n */\r\nexport function liftFontSize(\r\n blocks: DefaultPartialBlock[],\r\n): DefaultPartialBlock[] {\r\n return mapPreservingRef(blocks as any[], (b) => mapBlock(b, \"lift\"));\r\n}\r\n","import { CellSelection, TableMap, deleteRow } from \"prosemirror-tables\";\r\nimport { findTableNodePos } from \"./prosemirror-table-utils\";\r\n\r\n/**\r\n * 표(tablePos)의 각 행(tbody > tr)의 렌더 높이(px)를 PM 행 순서대로 측정한다.\r\n * 열 삭제로 rowspan 병합 셀이 collapse될 때 높이를 보존하기 위해 쓴다.\r\n * 측정 불가 시 null(→ 호출부는 높이 보존을 건너뜀).\r\n */\r\nfunction measureRowHeights(view: any, tablePos: number): number[] | null {\r\n try {\r\n const at = view?.domAtPos?.(tablePos + 1);\r\n let el: any = at?.node;\r\n if (el && el.nodeType === 3) el = el.parentElement;\r\n const tableEl: HTMLTableElement | null = el?.closest?.(\"table\") ?? null;\r\n const body = tableEl?.tBodies?.[0];\r\n if (!body) return null;\r\n return Array.from(body.rows).map((tr) =>\r\n Math.round(tr.getBoundingClientRect().height),\r\n );\r\n } catch {\r\n return null;\r\n }\r\n}\r\n\r\n/**\r\n * 표에서 한 열(col)을 제거한 **새 table 노드를 재구성**해 교체하는 트랜잭션을 만든다.\r\n *\r\n * 왜 재구성인가:\r\n * prosemirror-tables의 removeColumn(및 그를 쓰는 deleteColumn)은 열의 셀들을 개별\r\n * tr.delete로 지운다. 그런데 BlockNote의 tableRow content는 `tableRow+`(셀 1개 이상\r\n * 필수)라, 어떤 행의 \"유일한 셀\"이 삭제 대상이면 ProseMirror가 행을 비울 수 없어 **빈\r\n * 셀 껍데기**를 남긴다. 인접 열이 rowspan 병합돼 있으면(예: 2·3열 세로병합 후 1열 삭제)\r\n * 이 빈 셀이 rowspan 셀과 충돌(collision)하고, tableEditing의 fixTables가 이를 원복해\r\n * **열이 삭제되지 않는다**(코어 BlockNote에서도 재현되는 버그).\r\n *\r\n * 재구성 규칙:\r\n * - col만 차지하는 셀(colspan 1) → 제거.\r\n * - col을 가로질러 colspan>1로 걸친 셀 → colspan-1(및 colwidth 보정)로 유지.\r\n * - 그 외 셀 → 그대로 유지.\r\n * - 모든 원본 셀이 제거돼 **비게 되는 행**은 행 자체를 삭제하고, 그 행을 rowspan으로\r\n * 덮던 셀들의 rowspan을 줄인다.\r\n * 모든 셀/표 attr(병합, 배경색, verticalAlignment, rowHeight, tableAlignment 등)은\r\n * 노드를 그대로 재생성하므로 보존된다.\r\n *\r\n * @returns 적용할 transaction, 처리 불가/무변경 시 null.\r\n */\r\nfunction buildDeleteColumnTr(\r\n state: any,\r\n tablePos: number,\r\n col: number,\r\n rowPx: number[] | null,\r\n): any {\r\n const table = state.doc.nodeAt(tablePos);\r\n if (!table || table.type.name !== \"table\") return null;\r\n const map = TableMap.get(table);\r\n const W = map.width;\r\n const H = map.height;\r\n if (col < 0 || col >= W || W <= 1) return null;\r\n\r\n // 1. 고유 셀 + 원점(top,left)·span 수집.\r\n type CellInfo = {\r\n node: any;\r\n top: number;\r\n left: number;\r\n rowspan: number;\r\n colspan: number;\r\n };\r\n const cells: CellInfo[] = [];\r\n const seen = new Set<number>();\r\n for (let r = 0; r < H; r++) {\r\n for (let c = 0; c < W; c++) {\r\n const pos = map.map[r * W + c];\r\n if (seen.has(pos)) continue;\r\n seen.add(pos);\r\n const node = table.nodeAt(pos);\r\n if (!node) continue;\r\n cells.push({\r\n node,\r\n top: r,\r\n left: c,\r\n rowspan: node.attrs.rowspan ?? 1,\r\n colspan: node.attrs.colspan ?? 1,\r\n });\r\n }\r\n }\r\n\r\n // 2. 행별 \"살아남는 원점 셀 수\" 계산 → 0이면 그 행은 죽는다(삭제).\r\n const spansCol = (c: CellInfo) => c.left <= col && col < c.left + c.colspan;\r\n const isDropped = (c: CellInfo) => spansCol(c) && c.colspan === 1;\r\n const survivingByRow = new Array(H).fill(0);\r\n for (const c of cells) if (!isDropped(c)) survivingByRow[c.top]++;\r\n const rowDies = survivingByRow.map((n) => n === 0);\r\n const newRowIndex: number[] = [];\r\n let ni = 0;\r\n for (let r = 0; r < H; r++) newRowIndex[r] = rowDies[r] ? -1 : ni++;\r\n const newH = ni;\r\n if (newH === 0) return null; // 표 전체가 비는 경우는 처리하지 않음.\r\n\r\n // 3. 새 행 배열 구성(원점 셀을 left 순서대로 push).\r\n const newRows: any[][] = Array.from({ length: newH }, () => []);\r\n for (const c of cells) {\r\n if (isDropped(c)) continue;\r\n const newColspan = c.colspan - (spansCol(c) ? 1 : 0);\r\n let dyingWithin = 0;\r\n for (let r = c.top; r < c.top + c.rowspan; r++) if (rowDies[r]) dyingWithin++;\r\n const newRowspan = Math.max(1, c.rowspan - dyingWithin);\r\n\r\n const attrs: any = { ...c.node.attrs, colspan: newColspan, rowspan: newRowspan };\r\n if (spansCol(c) && Array.isArray(attrs.colwidth) && attrs.colwidth.length) {\r\n const cw = attrs.colwidth.slice();\r\n cw.splice(col - c.left, 1);\r\n attrs.colwidth = cw.some((w: number) => w > 0) ? cw : null;\r\n }\r\n // 높이 보존: rowspan이 줄어든(=빈 행이 제거된) 셀은 collapse로 키를 잃는다.\r\n // 원래 차지하던 행들의 렌더 높이 합을 rowHeight로 박아 시각 높이를 유지한다.\r\n if (dyingWithin > 0 && rowPx) {\r\n let sum = 0;\r\n let ok = true;\r\n for (let r = c.top; r < c.top + c.rowspan; r++) {\r\n if (typeof rowPx[r] !== \"number\") {\r\n ok = false;\r\n break;\r\n }\r\n sum += rowPx[r];\r\n }\r\n if (ok && sum > 0) attrs.rowHeight = Math.round(sum);\r\n }\r\n const target = newRowIndex[c.top];\r\n if (target >= 0) newRows[target].push(c.node.type.create(attrs, c.node.content));\r\n }\r\n\r\n // 4. 새 table 노드 생성 후 교체.\r\n const rowType = table.child(0)?.type;\r\n if (!rowType) return null;\r\n const rowNodes = newRows.map((rowCells) => rowType.create(null, rowCells));\r\n const newTable = table.type.create(table.attrs, rowNodes);\r\n\r\n const tr = state.tr;\r\n tr.replaceWith(tablePos, tablePos + table.nodeSize, newTable);\r\n return tr.docChanged ? tr : null;\r\n}\r\n\r\n/**\r\n * 행/열 삭제를 대상 표에서 직접(결정적으로) 수행한다.\r\n *\r\n * 배경(중요):\r\n * - 커스텀 포커스 기반 그립 메뉴가 열리면 ProseMirror 선택이 셀 밖으로 빠질 수 있어,\r\n * 코어 `tableHandles.removeRowOrColumn`(선택/`view.tablePos` 의존)이 실패한다.\r\n * - 인접 열에 rowspan 병합이 있으면 코어 deleteColumn이 첫 열을 못 지운다(위 참고).\r\n *\r\n * 삭제 대상 표는 (1) 컨트롤러가 노출한 포커스 표 id → (2) 선택 → (3) view.tablePos\r\n * 순으로 찾는다. 열 삭제는 위 재구성 방식(buildDeleteColumnTr)으로 병합 셀까지 안전하게\r\n * 처리하고, 행 삭제는 코어 deleteRow(행 전체를 한 번에 제거 → 빈-셀 문제 없음)를 쓴다.\r\n *\r\n * @returns 처리 성공 여부. 표를 못 찾으면 false(호출부가 코어 기본 동작으로 폴백).\r\n */\r\nexport function removeFocusedRowOrColumn(\r\n editor: any,\r\n index: number,\r\n direction: \"row\" | \"column\",\r\n): boolean {\r\n const tiptap = editor?._tiptapEditor;\r\n if (!tiptap) return false;\r\n const { state } = tiptap;\r\n\r\n // 1) 컨트롤러가 노출한 포커스 표 id(가장 권위 있음).\r\n let tablePos = -1;\r\n const activeId = editor?.__lumirActiveTableId;\r\n if (activeId) {\r\n const p = findTableNodePos(tiptap, activeId);\r\n if (p >= 0 && state.doc.nodeAt(p)?.type.name === \"table\") tablePos = p;\r\n }\r\n // 2) 현재 선택을 감싸는 table.\r\n if (tablePos < 0) {\r\n const $from = state.selection.$from;\r\n for (let d = $from.depth; d > 0; d--) {\r\n if ($from.node(d).type.name === \"table\") {\r\n tablePos = $from.before(d);\r\n break;\r\n }\r\n }\r\n }\r\n // 3) 코어 hover 추적 위치로 폴백.\r\n if (tablePos < 0) {\r\n const vp = editor?.tableHandles?.view?.tablePos;\r\n if (typeof vp === \"number\" && state.doc.nodeAt(vp)?.type.name === \"table\") {\r\n tablePos = vp;\r\n }\r\n }\r\n if (tablePos < 0) return false;\r\n\r\n try {\r\n if (direction === \"column\") {\r\n const rowPx = measureRowHeights(tiptap.view, tablePos);\r\n const tr = buildDeleteColumnTr(state, tablePos, index, rowPx);\r\n if (!tr) return false;\r\n tiptap.view.dispatch(tr);\r\n return true;\r\n }\r\n // 행 삭제: 대상 표 기준 CellSelection 구성 후 코어 deleteRow.\r\n const tableInside = state.doc.resolve(tablePos + 1);\r\n const rowStart = state.doc.resolve(tableInside.posAtIndex(index) + 1);\r\n const cellPos = state.doc.resolve(rowStart.posAtIndex(0));\r\n const selState = state.apply(\r\n state.tr.setSelection(new CellSelection(cellPos)),\r\n );\r\n return deleteRow(selState, (tr: any) => tiptap.view.dispatch(tr));\r\n } catch {\r\n return false;\r\n }\r\n}\r\n","/**\r\n * 엑셀/스프레드시트 표 붙여넣기 서식 정규화\r\n *\r\n * 엑셀은 셀 서식을 CSS 인라인 스타일(color/background/text-align 등)로 내보내지만,\r\n * BlockNote 테이블 셀은 자기 속성(data-text-color / data-background-color /\r\n * data-text-alignment)만 파싱하고 임의의 CSS 색은 무시한다.\r\n * 이 모듈은 엑셀 HTML을 BlockNote가 인식하는 형태로 변환한다:\r\n * - CSS color/background → 가장 가까운 BlockNote 팔레트 색 → data-* 속성\r\n * - text-align / align → data-text-alignment\r\n * - font-weight/font-style/underline → <strong>/<em>/<u> 래핑 (마크 보존 강화)\r\n *\r\n * BlockNote 색은 고정 팔레트(10색)뿐이라 임의 hex는 \"근사치\"로 양자화된다.\r\n */\r\n\r\ntype RGB = [number, number, number];\r\n\r\nconst NAMED_COLORS: Record<string, string> = {\r\n black: \"#000000\",\r\n white: \"#ffffff\",\r\n red: \"#ff0000\",\r\n green: \"#008000\",\r\n blue: \"#0000ff\",\r\n yellow: \"#ffff00\",\r\n orange: \"#ffa500\",\r\n purple: \"#800080\",\r\n gray: \"#808080\",\r\n grey: \"#808080\",\r\n};\r\n\r\n/** CSS 색 문자열(#hex, rgb(), 일부 named)을 RGB로 파싱. 실패 시 null */\r\nexport function parseCssColorToRgb(input?: string | null): RGB | null {\r\n if (!input) return null;\r\n const s = input.trim().toLowerCase();\r\n if (!s || s === \"transparent\" || s === \"none\" || s === \"inherit\") return null;\r\n\r\n let m = s.match(/^#([0-9a-f]{3}|[0-9a-f]{6})$/);\r\n if (m) {\r\n let h = m[1];\r\n if (h.length === 3)\r\n h = h\r\n .split(\"\")\r\n .map((c) => c + c)\r\n .join(\"\");\r\n return [\r\n parseInt(h.slice(0, 2), 16),\r\n parseInt(h.slice(2, 4), 16),\r\n parseInt(h.slice(4, 6), 16),\r\n ];\r\n }\r\n\r\n m = s.match(/^rgba?\\(([^)]+)\\)$/);\r\n if (m) {\r\n const parts = m[1].split(\",\").map((x) => parseFloat(x.trim()));\r\n if (parts.length >= 3 && parts.slice(0, 3).every((n) => !isNaN(n))) {\r\n return [parts[0], parts[1], parts[2]] as RGB;\r\n }\r\n return null;\r\n }\r\n\r\n if (NAMED_COLORS[s]) return parseCssColorToRgb(NAMED_COLORS[s]);\r\n return null;\r\n}\r\n\r\n/** RGB(0-255) → HSL. h:0-360, s:0-1, l:0-1 */\r\nfunction rgbToHsl([r, g, b]: RGB): [number, number, number] {\r\n r /= 255;\r\n g /= 255;\r\n b /= 255;\r\n const max = Math.max(r, g, b);\r\n const min = Math.min(r, g, b);\r\n const l = (max + min) / 2;\r\n let h = 0;\r\n let s = 0;\r\n const d = max - min;\r\n if (d !== 0) {\r\n s = l > 0.5 ? d / (2 - max - min) : d / (max + min);\r\n switch (max) {\r\n case r:\r\n h = ((g - b) / d + (g < b ? 6 : 0)) * 60;\r\n break;\r\n case g:\r\n h = ((b - r) / d + 2) * 60;\r\n break;\r\n default:\r\n h = ((r - g) / d + 4) * 60;\r\n break;\r\n }\r\n }\r\n return [h, s, l];\r\n}\r\n\r\n// 유채색 팔레트의 기준 색상(hue). TEXT_COLORS의 채도 높은 hex 기준.\r\nconst HUE_REFERENCE: { value: string; hue: number }[] = [\r\n { value: \"red\", hue: 0 },\r\n { value: \"brown\", hue: 17 },\r\n { value: \"orange\", hue: 30 },\r\n { value: \"yellow\", hue: 46 },\r\n { value: \"green\", hue: 150 },\r\n { value: \"blue\", hue: 197 },\r\n { value: \"purple\", hue: 262 },\r\n { value: \"pink\", hue: 324 },\r\n];\r\n\r\nfunction hueDist(a: number, b: number): number {\r\n const d = Math.abs(a - b) % 360;\r\n return d > 180 ? 360 - d : d;\r\n}\r\n\r\n/**\r\n * 임의 색 → BlockNote 팔레트 value (hue 기반 매칭).\r\n * 무채색(낮은 채도)은 검정류=default, 그 외 회색=gray.\r\n */\r\nfunction paletteValueFromRgb(rgb: RGB): string {\r\n const [h, s, l] = rgbToHsl(rgb);\r\n if (s < 0.15) {\r\n // 무채색\r\n if (l < 0.35) return \"default\"; // 검정류\r\n if (l > 0.85) return \"default\"; // 흰색류(무배경/기본 글자배경)\r\n return \"gray\";\r\n }\r\n let best = \"gray\";\r\n let bestDist = Infinity;\r\n for (const ref of HUE_REFERENCE) {\r\n const d = hueDist(h, ref.hue);\r\n if (d < bestDist) {\r\n bestDist = d;\r\n best = ref.value;\r\n }\r\n }\r\n return best;\r\n}\r\n\r\n/** 임의 색 → 가장 가까운 텍스트 팔레트 value (검정류는 default) */\r\nexport function nearestTextColorValue(rgb: RGB): string {\r\n return paletteValueFromRgb(rgb);\r\n}\r\n\r\n/** 임의 색 → 가장 가까운 배경 팔레트 value (흰색/근사 흰색은 default=무배경) */\r\nexport function nearestBackgroundColorValue(rgb: RGB): string {\r\n const [, s, l] = rgbToHsl(rgb);\r\n // 흰색/근사 흰색은 배경 없음으로 간주\r\n if (s < 0.12 && l > 0.85) return \"default\";\r\n return paletteValueFromRgb(rgb);\r\n}\r\n\r\n/** \"k:v; k2:v2\" → { k: v } */\r\nfunction parseStyle(style: string): Record<string, string> {\r\n const out: Record<string, string> = {};\r\n style.split(\";\").forEach((decl) => {\r\n const idx = decl.indexOf(\":\");\r\n if (idx === -1) return;\r\n const k = decl.slice(0, idx).trim().toLowerCase();\r\n const v = decl.slice(idx + 1).trim();\r\n if (k) out[k] = v;\r\n });\r\n return out;\r\n}\r\n\r\n/** background 단축 속성에서 색만 추출 시도 */\r\nfunction colorFromBackgroundShorthand(value: string): RGB | null {\r\n if (!value) return null;\r\n const direct = parseCssColorToRgb(value);\r\n if (direct) return direct;\r\n for (const token of value.split(/\\s+/)) {\r\n const rgb = parseCssColorToRgb(token);\r\n if (rgb) return rgb;\r\n }\r\n return null;\r\n}\r\n\r\n/** rgba alpha=0 / transparent 여부 */\r\nfunction isTransparentColor(css?: string | null): boolean {\r\n if (!css) return true;\r\n const s = css.trim().toLowerCase();\r\n if (s === \"transparent\" || s === \"none\") return true;\r\n const m = s.match(/^rgba?\\(([^)]+)\\)$/);\r\n if (m) {\r\n const p = m[1].split(\",\").map((x) => parseFloat(x.trim()));\r\n if (p.length >= 4 && p[3] === 0) return true;\r\n }\r\n return false;\r\n}\r\n\r\n/** CSS text-align 값을 BlockNote 정렬 value로 (left/start는 기본이라 빈 문자열) */\r\nfunction normalizeAlign(ta?: string | null): string {\r\n const v = (ta || \"\").trim().toLowerCase();\r\n if (v === \"right\" || v === \"end\") return \"right\";\r\n if (v === \"center\") return \"center\";\r\n if (v === \"justify\") return \"justify\";\r\n return \"\";\r\n}\r\n\r\n/** CSS vertical-align → BlockNote verticalAlignment (top/기본은 null로 스킵) */\r\nfunction mapVerticalAlign(v?: string | null): \"middle\" | \"bottom\" | null {\r\n const s = (v || \"\").trim().toLowerCase();\r\n if (s === \"middle\" || s === \"center\") return \"middle\";\r\n if (s === \"bottom\") return \"bottom\";\r\n return null;\r\n}\r\n\r\n/** font-size 문자열을 px로. computed는 px, inline은 pt/px. 실패 시 null. */\r\nfunction fontSizeToPx(raw?: string | null): number | null {\r\n if (!raw) return null;\r\n const m = String(raw).trim().match(/^([\\d.]+)\\s*(px|pt)?$/i);\r\n if (!m) return null;\r\n const v = parseFloat(m[1]);\r\n if (!Number.isFinite(v) || v <= 0) return null;\r\n return (m[2] || \"px\").toLowerCase() === \"pt\" ? v * (96 / 72) : v;\r\n}\r\n\r\ninterface CellFormat {\r\n bgRgb: RGB | null;\r\n bgTransparent: boolean;\r\n colorRgb: RGB | null;\r\n align: string;\r\n bold: boolean;\r\n italic: boolean;\r\n underline: boolean;\r\n /** 셀 글자 크기(px). 본문 기본(14px) 근처면 적용하지 않는다. */\r\n fontSizePx: number | null;\r\n /** 셀 세로 정렬(middle/bottom만; top/기본은 null). */\r\n verticalAlign: \"middle\" | \"bottom\" | null;\r\n}\r\n\r\n/** 셀 서식을 BlockNote data-* 속성/마크로 적용 */\r\nfunction applyCellFormatting(el: HTMLElement, fmt: CellFormat): void {\r\n if (fmt.bgRgb && !fmt.bgTransparent) {\r\n const v = nearestBackgroundColorValue(fmt.bgRgb);\r\n if (v !== \"default\") el.setAttribute(\"data-background-color\", v);\r\n }\r\n if (fmt.colorRgb) {\r\n const v = nearestTextColorValue(fmt.colorRgb);\r\n if (v !== \"default\") el.setAttribute(\"data-text-color\", v);\r\n }\r\n if ([\"right\", \"center\", \"justify\"].includes(fmt.align)) {\r\n el.setAttribute(\"data-text-alignment\", fmt.align);\r\n }\r\n if ((fmt.bold || fmt.italic || fmt.underline) && el.innerHTML.trim()) {\r\n let inner = el.innerHTML;\r\n if (fmt.underline) inner = `<u>${inner}</u>`;\r\n if (fmt.italic) inner = `<em>${inner}</em>`;\r\n if (fmt.bold) inner = `<strong>${inner}</strong>`;\r\n el.innerHTML = inner;\r\n }\r\n // 세로 정렬: VerticalAlignmentExtension parseHTML이 data-vertical-alignment를 읽는다.\r\n if (fmt.verticalAlign) {\r\n el.setAttribute(\"data-vertical-alignment\", fmt.verticalAlign);\r\n }\r\n // 글자 크기: 본문 기본(14px)과 1px 넘게 다르면 셀 내용을 FontSize 스타일 span으로 래핑.\r\n // (FontSize 스타일은 data-style-type/data-value 형태로 붙여넣기 왕복됨)\r\n if (\r\n fmt.fontSizePx &&\r\n Math.abs(fmt.fontSizePx - 14) > 1 &&\r\n el.innerHTML.trim()\r\n ) {\r\n const v = `${Math.round(fmt.fontSizePx)}px`;\r\n el.innerHTML =\r\n `<span data-style-type=\"fontSize\" data-value=\"${v}\" style=\"font-size:${v}\">` +\r\n el.innerHTML +\r\n `</span>`;\r\n }\r\n}\r\n\r\n/** getComputedStyle로 셀 서식 읽기 (클래스/<style>/inline 모두 반영) */\r\nfunction readComputedFormat(el: HTMLElement): CellFormat {\r\n const cs = getComputedStyle(el);\r\n const fw = cs.fontWeight;\r\n const fwNum = parseInt(fw, 10);\r\n const decoration = cs.textDecorationLine || cs.textDecoration || \"\";\r\n return {\r\n bgRgb: parseCssColorToRgb(cs.backgroundColor),\r\n bgTransparent: isTransparentColor(cs.backgroundColor),\r\n colorRgb: parseCssColorToRgb(cs.color),\r\n align: normalizeAlign(cs.textAlign),\r\n bold: fw === \"bold\" || fw === \"bolder\" || (!isNaN(fwNum) && fwNum >= 600),\r\n italic: (cs.fontStyle || \"\").toLowerCase().includes(\"italic\"),\r\n underline: decoration.toLowerCase().includes(\"underline\"),\r\n fontSizePx: fontSizeToPx(cs.fontSize),\r\n // ⚠️ computed vertical-align은 td 기본값이 \"middle\"이라 셀마다 잘못 붙는다.\r\n // 명시적 inline style / valign 속성만 읽는다(기본값 노이즈 방지).\r\n verticalAlign: mapVerticalAlign(\r\n el.style?.verticalAlign || el.getAttribute(\"valign\"),\r\n ),\r\n };\r\n}\r\n\r\n/** inline style 속성만으로 셀 서식 읽기 (computed 불가 환경 폴백) */\r\nfunction readInlineFormat(el: HTMLElement): CellFormat {\r\n const sm = parseStyle(el.getAttribute(\"style\") || \"\");\r\n const bgRaw = sm[\"background-color\"] || sm[\"background\"] || \"\";\r\n const bgRgb =\r\n colorFromBackgroundShorthand(bgRaw) ||\r\n parseCssColorToRgb(el.getAttribute(\"bgcolor\"));\r\n const fw = (sm[\"font-weight\"] || \"\").toLowerCase();\r\n const decoration = sm[\"text-decoration\"] || sm[\"text-decoration-line\"] || \"\";\r\n return {\r\n bgRgb,\r\n bgTransparent: !bgRaw && !el.getAttribute(\"bgcolor\"),\r\n colorRgb: parseCssColorToRgb(sm[\"color\"]),\r\n align: normalizeAlign(sm[\"text-align\"] || el.getAttribute(\"align\")),\r\n bold: fw === \"bold\" || fw === \"bolder\" || parseInt(fw, 10) >= 600,\r\n italic: (sm[\"font-style\"] || \"\").toLowerCase().includes(\"italic\"),\r\n underline: decoration.toLowerCase().includes(\"underline\"),\r\n fontSizePx: fontSizeToPx(sm[\"font-size\"]),\r\n verticalAlign: mapVerticalAlign(\r\n sm[\"vertical-align\"] || el.getAttribute(\"valign\"),\r\n ),\r\n };\r\n}\r\n\r\n/**\r\n * 엑셀 표 HTML을 BlockNote가 인식하는 서식으로 정규화한다.\r\n *\r\n * 엑셀(데스크톱)은 셀 서식을 인라인 style이 아니라 <style> 블록의 클래스\r\n * (.xlNN { background:#FFFF00 })로 내보내는 경우가 많다. 따라서 표를 격리된\r\n * Shadow DOM(오프스크린)에 잠깐 넣고 getComputedStyle로 최종 서식을 읽어\r\n * 인라인·클래스·mso 형식을 모두 일괄 처리한다. Shadow DOM이라 엑셀 <style>이\r\n * 페이지로 누출되지 않는다. 실패/비브라우저 환경에서는 inline 파싱으로 폴백.\r\n */\r\nexport function normalizeExcelTableHtml(html: string): string {\r\n if (!html || typeof DOMParser === \"undefined\") return html;\r\n try {\r\n const doc = new DOMParser().parseFromString(html, \"text/html\");\r\n doc.querySelectorAll(\"script\").forEach((s) => s.remove());\r\n if (!doc.querySelector(\"table\")) return html;\r\n\r\n // 우선 경로: 실제 DOM + getComputedStyle\r\n if (\r\n typeof document !== \"undefined\" &&\r\n document.body &&\r\n typeof HTMLElement !== \"undefined\" &&\r\n typeof HTMLElement.prototype.attachShadow === \"function\"\r\n ) {\r\n let host: HTMLElement | null = null;\r\n try {\r\n host = document.createElement(\"div\");\r\n host.setAttribute(\"aria-hidden\", \"true\");\r\n host.style.cssText =\r\n \"position:absolute;left:-99999px;top:0;width:0;height:0;overflow:hidden;opacity:0;pointer-events:none;font-size:14px\";\r\n const shadow = host.attachShadow({ mode: \"open\" });\r\n const styles = Array.from(doc.querySelectorAll(\"style\"))\r\n .map((s) => s.outerHTML)\r\n .join(\"\");\r\n shadow.innerHTML = styles + doc.body.innerHTML;\r\n document.body.appendChild(host);\r\n\r\n shadow.querySelectorAll(\"td, th\").forEach((node) => {\r\n applyCellFormatting(node as HTMLElement, readComputedFormat(node as HTMLElement));\r\n });\r\n\r\n const out = Array.from(shadow.querySelectorAll(\"table\"))\r\n .map((t) => t.outerHTML)\r\n .join(\"\");\r\n return out || html;\r\n } finally {\r\n if (host && host.parentNode) host.parentNode.removeChild(host);\r\n }\r\n }\r\n\r\n // 폴백 경로: inline style만 파싱\r\n doc.querySelectorAll(\"td, th\").forEach((node) => {\r\n applyCellFormatting(node as HTMLElement, readInlineFormat(node as HTMLElement));\r\n });\r\n return (\r\n Array.from(doc.querySelectorAll(\"table\"))\r\n .map((t) => t.outerHTML)\r\n .join(\"\") || html\r\n );\r\n } catch {\r\n return html;\r\n }\r\n}\r\n","/**\r\n * 붙여넣은 표를 **에디터 콘텐츠 너비에 맞춰** 각 열 비율을 유지한 채 축소한다.\r\n *\r\n * 배경: Word/docx 등에서 복사한 표는 열 너비(pt/px/%)가 에디터보다 넓어 가로 스크롤이\r\n * 생긴다. BlockNote 표는 열 너비를 블록 content의 `columnWidths` 배열로 모델링하며,\r\n * 붙여넣기 HTML 파서는 셀 width를 columnWidths로 읽지 않는다(붙여넣은 표는 기본폭).\r\n * 그래서 (1) 붙여넣기 전에 HTML에서 열별 원본 너비를 읽어 비율 유지로 maxWidth 이하로\r\n * 스케일하고, (2) 붙여넣은 뒤 새 표 블록의 `columnWidths`를 그 값으로 설정한다.\r\n * table-layout:fixed라 columnWidths 합이 에디터 너비 이하면 가로 스크롤이 없다.\r\n */\r\n\r\nconst MIN_COL_PX = 24;\r\n\r\nfunction toPx(raw: string | null | undefined, maxWidth: number): number | null {\r\n if (!raw) return null;\r\n const m = String(raw).trim().match(/^([\\d.]+)\\s*(pt|px|%)?$/i);\r\n if (!m) return null;\r\n const v = parseFloat(m[1]);\r\n if (!Number.isFinite(v) || v <= 0) return null;\r\n const unit = (m[2] || \"px\").toLowerCase();\r\n if (unit === \"pt\") return v * (96 / 72);\r\n if (unit === \"%\") return (v / 100) * maxWidth;\r\n return v;\r\n}\r\n\r\nfunction elWidthPx(el: Element, maxWidth: number): number | null {\r\n const styleW = (el as HTMLElement).style?.width;\r\n return toPx(styleW, maxWidth) ?? toPx(el.getAttribute(\"width\"), maxWidth);\r\n}\r\n\r\n/** 표의 열별 원본 너비(px)를 colgroup<col> 또는 첫 행 셀에서 읽는다. 못 구하면 null. */\r\nfunction readColumnWidths(table: HTMLTableElement, maxWidth: number): number[] | null {\r\n const colEls = table.querySelector(\"colgroup\")?.querySelectorAll(\"col\");\r\n if (colEls && colEls.length > 0) {\r\n const widths: number[] = [];\r\n let ok = true;\r\n colEls.forEach((c) => {\r\n const span = parseInt(c.getAttribute(\"span\") || \"1\", 10) || 1;\r\n const w = elWidthPx(c, maxWidth);\r\n if (w == null) ok = false;\r\n for (let i = 0; i < span; i++) widths.push(w ?? 0);\r\n });\r\n if (ok && widths.length > 0) return widths;\r\n }\r\n const firstRow = table.querySelector(\"tr\");\r\n if (!firstRow) return null;\r\n const widths: number[] = [];\r\n let ok = true;\r\n Array.from(firstRow.children).forEach((cell) => {\r\n if (cell.tagName !== \"TD\" && cell.tagName !== \"TH\") return;\r\n const span = parseInt(cell.getAttribute(\"colspan\") || \"1\", 10) || 1;\r\n const w = elWidthPx(cell, maxWidth);\r\n if (w == null) ok = false;\r\n const per = (w ?? 0) / span;\r\n for (let i = 0; i < span; i++) widths.push(per);\r\n });\r\n return ok && widths.length > 0 ? widths : null;\r\n}\r\n\r\n/** 원본 너비를 maxWidth 이하로 비율 유지 스케일한 px 배열. 넘지 않으면 원본 유지. */\r\nfunction fitWidths(widths: number[], maxWidth: number): number[] {\r\n const total = widths.reduce((a, b) => a + b, 0);\r\n if (total <= 0) return widths;\r\n const scale = total > maxWidth ? maxWidth / total : 1;\r\n return widths.map((w) => Math.max(MIN_COL_PX, Math.round(w * scale)));\r\n}\r\n\r\n/**\r\n * 붙여넣기 HTML의 각 `<table>`에 대해, 에디터 너비(maxWidth)에 맞춘 columnWidths 배열을\r\n * HTML 등장 순서로 반환한다. 너비를 못 읽은 표는 null.\r\n */\r\nexport function computeFittedColumnWidthsPerTable(\r\n html: string,\r\n maxWidth: number,\r\n): (number[] | null)[] {\r\n if (!html || typeof DOMParser === \"undefined\" || !(maxWidth > 0)) return [];\r\n let doc: Document;\r\n try {\r\n doc = new DOMParser().parseFromString(html, \"text/html\");\r\n } catch {\r\n return [];\r\n }\r\n return Array.from(doc.querySelectorAll(\"table\")).map((t) => {\r\n const widths = readColumnWidths(t as HTMLTableElement, maxWidth);\r\n return widths ? fitWidths(widths, maxWidth) : null;\r\n });\r\n}\r\n\r\n/** blocks(및 children)에서 table 블록을 등장 순서로 모은다. */\r\nexport function collectTableBlocks(blocks: any[]): any[] {\r\n const out: any[] = [];\r\n const walk = (bs: any[]) => {\r\n for (const b of bs) {\r\n if (b?.type === \"table\") out.push(b);\r\n if (b?.children?.length) walk(b.children);\r\n }\r\n };\r\n walk(blocks || []);\r\n return out;\r\n}\r\n\r\n/**\r\n * 붙여넣기 직후 호출: beforeIds에 없던 새 표 블록들에 perTable[i] columnWidths를 적용한다.\r\n * 열 개수가 맞을 때만 적용(불일치 시 건너뜀).\r\n */\r\nexport function applyFittedWidthsToNewTables(\r\n editor: any,\r\n beforeIds: Set<string>,\r\n perTable: (number[] | null)[],\r\n): void {\r\n if (!editor || perTable.length === 0) return;\r\n const newTables = collectTableBlocks(editor.document).filter(\r\n (b) => !beforeIds.has(b.id),\r\n );\r\n newTables.forEach((tb, i) => {\r\n const widths = perTable[i];\r\n const current = tb?.content?.columnWidths;\r\n if (\r\n widths &&\r\n Array.isArray(current) &&\r\n current.length === widths.length\r\n ) {\r\n try {\r\n editor.updateBlock(tb, {\r\n type: \"table\",\r\n content: { ...tb.content, columnWidths: widths },\r\n });\r\n } catch {\r\n /* 단일 표 실패가 전체 붙여넣기를 막지 않도록 무시 */\r\n }\r\n }\r\n });\r\n}\r\n"],"mappings":"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","names":["FontSizeButton","import_react","import_core","import_react","import_core","import_react","endpoint","import_react","import_jsx_runtime","tr","import_prosemirror_state","import_core","import_react","import_jsx_runtime","import_react","import_jsx_runtime","handleMouseMove","import_react","import_jsx_runtime","import_jsx_runtime","import_react","import_jsx_runtime","handleMouseDown","import_react","import_jsx_runtime","handleMouseDown","import_react","import_jsx_runtime","iconMap","titleMap","handleMouseDown","import_react","import_jsx_runtime","iconMap","titleMap","handleMouseDown","import_react","import_jsx_runtime","handleMouseDown","import_react","import_jsx_runtime","handleMouseDown","import_react","import_jsx_runtime","handleMouseDown","import_react","import_jsx_runtime","handleMouseDown","import_react","import_jsx_runtime","handleMouseDown","import_react","import_jsx_runtime","handleMouseDown","import_react","import_jsx_runtime","handleMouseDown","found","import_jsx_runtime","import_core","import_core","import_prosemirror_state","import_prosemirror_view","offset","import_prosemirror_state","import_prosemirror_view","import_prosemirror_tables","import_prosemirror_state","tr","import_core","import_prosemirror_state","import_prosemirror_view","import_core","import_prosemirror_tables","import_prosemirror_state","schema","import_react","import_core","import_react","import_jsx_runtime","import_react","import_jsx_runtime","icons","import_react","import_jsx_runtime","icons","tooltips","import_react","import_jsx_runtime","DEFAULT_LABEL","toLabel","FontSizeButton","import_core","import_react","import_jsx_runtime","import_jsx_runtime","FontSizeButton","import_react","import_jsx_runtime","import_react","import_react","import_jsx_runtime","th","cellEl","tbodyEl","isMeaningful","fs","import_prosemirror_tables","widths","ok","import_jsx_runtime","editor","BlockSideMenu"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/components/LumirEditor.tsx","../src/utils/cn.ts","../src/utils/s3-uploader.ts","../src/blocks/HtmlPreview.tsx","../src/blocks/LinkPreview.tsx","../src/blocks/defaultLogo.ts","../src/blocks/VideoBlock.tsx","../src/blocks/columns/ColumnList.ts","../src/blocks/columns/columnNormalization.ts","../src/blocks/columns/columnDnd.ts","../src/blocks/columns/mergeColumns.ts","../src/blocks/columns/Column.ts","../src/styles/FontSizeStyle.tsx","../src/components/FloatingMenu/index.tsx","../src/components/FloatingMenu/Icons.tsx","../src/components/FloatingMenu/components/ToolbarDivider.tsx","../src/components/FloatingMenu/components/UndoRedoButtons.tsx","../src/components/FloatingMenu/components/TextStyleButton.tsx","../src/components/FloatingMenu/components/AlignButton.tsx","../src/utils/prosemirror-table-utils.ts","../src/components/FloatingMenu/components/ListButton.tsx","../src/components/FloatingMenu/components/ImageButton.tsx","../src/components/FloatingMenu/components/ColorButton.tsx","../src/constants/colors.ts","../src/components/FloatingMenu/components/FontSizeButton.tsx","../src/components/FloatingMenu/components/LinkButton.tsx","../src/components/FloatingMenu/components/TableButton.tsx","../src/components/FloatingMenu/components/HTMLImportButton.tsx","../src/components/FloatingMenu/components/BlockTypeSelect.tsx","../src/errors/LumirEditorError.ts","../src/extensions/VerticalAlignmentExtension.ts","../src/extensions/RowHeightExtension.ts","../src/extensions/rowResizing.ts","../src/constants/limits.ts","../src/extensions/tableScaling.ts","../src/extensions/tableCellAttrPreserve.ts","../src/extensions/TableAlignmentExtension.ts","../src/extensions/TableSelectAllExtension.ts","../src/blocks/columns/insertColumns.ts","../src/components/CustomFormattingToolbar.tsx","../src/components/TextAlignButtonWithVA.tsx","../src/components/VerticalAlignButton.tsx","../src/components/TableAlignButton.tsx","../src/components/FontSizeButton.tsx","../src/components/color/LumirColorControls.tsx","../src/components/LumirDragHandleMenu.tsx","../src/components/LumirTableHandlesController.tsx","../src/components/hooks/useFocusedCellHandlePositioning.ts","../src/utils/table-vertical-alignment.ts","../src/utils/font-size-serialization.ts","../src/utils/table-delete.ts","../src/utils/excel-paste.ts","../src/utils/table-paste-fit.ts"],"sourcesContent":["\"use client\";\r\n\r\n// 컴포넌트 및 유틸리티 export\r\nexport {\r\n default as LumirEditor,\r\n ContentUtils,\r\n EditorConfig,\r\n} from \"./components/LumirEditor\";\r\nexport { cn } from \"./utils/cn\";\r\nexport { createS3Uploader } from \"./utils/s3-uploader\";\r\nexport { HtmlPreviewBlock, schema as HtmlPreviewSchema } from \"./blocks/HtmlPreview\";\r\nexport { LinkPreviewBlock, fetchLinkMetadata, clearMetadataCache } from \"./blocks/LinkPreview\";\r\nexport type { LinkMetadata } from \"./blocks/LinkPreview\";\r\n\r\n// FloatingMenu 및 관련 컴포넌트 export\r\nexport { FloatingMenu } from \"./components/FloatingMenu\";\r\n\r\n// 글자 크기(fontSize) 관련 export\r\n// - FontSize: 커스텀 스타일 스펙, FONT_SIZE_PRESETS: 툴바 프리셋\r\n// - liftFontSize/lowerFontSize: 직렬화 호환 레이어(형제 키 ↔ styles.fontSize).\r\n// BlockNote 외부 렌더러에서 저장 JSON을 재수화할 때 liftFontSize를 사용한다.\r\nexport {\r\n FontSize,\r\n FONT_SIZE_PRESETS,\r\n FONT_SIZE_MIN,\r\n FONT_SIZE_MAX,\r\n FONT_SIZE_DEFAULT_PX,\r\n FONT_SIZE_STEP,\r\n parseFontSizePx,\r\n clampFontSizePx,\r\n toFontSizeValue,\r\n} from \"./styles/FontSizeStyle\";\r\nexport type { FontSizePreset } from \"./styles/FontSizeStyle\";\r\nexport { FontSizeButton } from \"./components/FontSizeButton\";\r\nexport {\r\n liftFontSize,\r\n lowerFontSize,\r\n} from \"./utils/font-size-serialization\";\r\nexport type { SerializedStyledText } from \"./utils/font-size-serialization\";\r\n\r\n// 에러 클래스 export\r\nexport { LumirEditorError } from \"./errors/LumirEditorError\";\r\nexport type { LumirErrorCode, LumirErrorDetails } from \"./errors/LumirEditorError\";\r\n\r\n// 색상 상수 export\r\nexport {\r\n TEXT_COLORS,\r\n BACKGROUND_COLORS,\r\n getHexFromColorValue,\r\n} from \"./constants/colors\";\r\nexport type { ColorItem } from \"./constants/colors\";\r\n\r\n// 타입 export (별도 파일에서 관리)\r\nexport type {\r\n LumirEditorProps,\r\n EditorType,\r\n DefaultPartialBlock,\r\n DefaultBlockSchema,\r\n DefaultInlineContentSchema,\r\n DefaultStyleSchema,\r\n PartialBlock,\r\n BlockNoteEditor,\r\n} from \"./types\";\r\nexport type { S3UploaderConfig } from \"./utils/s3-uploader\";\r\n","\"use client\";\r\n\r\nimport { useEffect, useMemo, useCallback, useState, useRef } from \"react\";\r\nimport {\r\n useCreateBlockNote,\r\n SideMenu as BlockSideMenu,\r\n SideMenuController,\r\n DragHandleButton,\r\n SuggestionMenuController,\r\n getDefaultReactSlashMenuItems,\r\n LinkToolbarController,\r\n FormattingToolbarController,\r\n useBlockNoteEditor,\r\n useComponentsContext,\r\n EditLinkButton,\r\n OpenLinkButton,\r\n DeleteLinkButton,\r\n} from \"@blocknote/react\";\r\nimport { BlockNoteView } from \"@blocknote/mantine\";\r\nimport { insertOrUpdateBlock } from \"@blocknote/core\";\r\nimport { ko, en } from \"@blocknote/core/locales\";\r\nimport { cn } from \"../utils/cn\";\r\n\r\nimport type { DefaultPartialBlock, LumirEditorProps } from \"../types\";\r\n\r\nimport { createS3Uploader } from \"../utils/s3-uploader\";\r\nimport { schema } from \"../blocks/HtmlPreview\";\r\nimport { FloatingMenu } from \"./FloatingMenu\";\r\nimport { LumirEditorError } from \"../errors/LumirEditorError\";\r\nimport { VerticalAlignmentExtension } from \"../extensions/VerticalAlignmentExtension\";\r\nimport { RowHeightExtension } from \"../extensions/RowHeightExtension\";\r\nimport { TableAlignmentExtension } from \"../extensions/TableAlignmentExtension\";\r\nimport { TableSelectAllExtension } from \"../extensions/TableSelectAllExtension\";\r\nimport { insertTwoColumns } from \"../blocks/columns/insertColumns\";\r\nimport { CustomFormattingToolbar } from \"./CustomFormattingToolbar\";\r\nimport { LumirDragHandleMenu } from \"./LumirDragHandleMenu\";\r\nimport { LumirTableHandlesController } from \"./LumirTableHandlesController\";\r\nimport {\r\n injectTableCellAttrs,\r\n injectTableBlockAttrs,\r\n} from \"../utils/table-vertical-alignment\";\r\nimport {\r\n liftFontSize,\r\n lowerFontSize,\r\n} from \"../utils/font-size-serialization\";\r\nimport { isInTableCell } from \"../utils/prosemirror-table-utils\";\r\nimport { removeFocusedRowOrColumn } from \"../utils/table-delete\";\r\nimport { normalizeExcelTableHtml } from \"../utils/excel-paste\";\r\nimport {\r\n computeFittedColumnWidthsPerTable,\r\n collectTableBlocks,\r\n applyFittedWidthsToNewTables,\r\n} from \"../utils/table-paste-fit\";\r\nimport {\r\n MAX_FILE_SIZE,\r\n MAX_VIDEO_FILE_SIZE,\r\n BLOCKED_EXTENSIONS,\r\n ALLOWED_VIDEO_MIME_TYPES,\r\n ALLOWED_VIDEO_EXTENSIONS,\r\n} from \"../constants/limits\";\r\n\r\n// #region agent log\r\nconst DEBUG_LOG = (loc: string, msg: string, data: Record<string, unknown>) => {\r\n const p = fetch(\"http://127.0.0.1:7686/ingest/1f8ee1c5-0cf0-4ae7-91ed-5ea7ed17130a\", {\r\n method: \"POST\",\r\n headers: { \"Content-Type\": \"application/json\", \"X-Debug-Session-Id\": \"b73262\" },\r\n body: JSON.stringify({\r\n sessionId: \"b73262\",\r\n location: loc,\r\n message: msg,\r\n data,\r\n timestamp: Date.now(),\r\n }),\r\n });\r\n if (p && typeof (p as Promise<unknown>).catch === \"function\") (p as Promise<unknown>).catch(() => {});\r\n};\r\n// #endregion\r\n\r\n// ==========================================\r\n// 유틸리티 클래스들\r\n// ==========================================\r\n\r\n/**\r\n * 콘텐츠 관리 유틸리티\r\n * 기본 블록 생성 및 콘텐츠 검증 로직을 담당\r\n */\r\nexport class ContentUtils {\r\n /**\r\n * JSON 문자열의 유효성을 검증합니다\r\n * @param jsonString 검증할 JSON 문자열\r\n * @returns 유효한 JSON 문자열인지 여부\r\n */\r\n static isValidJSONString(jsonString: string): boolean {\r\n try {\r\n const parsed = JSON.parse(jsonString);\r\n return Array.isArray(parsed);\r\n } catch {\r\n return false;\r\n }\r\n }\r\n\r\n /**\r\n * JSON 문자열을 DefaultPartialBlock 배열로 파싱합니다\r\n * @param jsonString JSON 문자열\r\n * @returns 파싱된 블록 배열 또는 null (파싱 실패 시)\r\n */\r\n static parseJSONContent(jsonString: string): DefaultPartialBlock[] | null {\r\n try {\r\n const parsed = JSON.parse(jsonString);\r\n if (Array.isArray(parsed)) {\r\n return parsed as DefaultPartialBlock[];\r\n }\r\n return null;\r\n } catch {\r\n return null;\r\n }\r\n }\r\n\r\n /**\r\n * 기본 paragraph 블록 생성\r\n * @returns 기본 설정이 적용된 DefaultPartialBlock\r\n */\r\n static createDefaultBlock(): DefaultPartialBlock {\r\n return {\r\n type: \"paragraph\",\r\n props: {\r\n textColor: \"default\",\r\n backgroundColor: \"default\",\r\n textAlignment: \"left\",\r\n },\r\n content: [{ type: \"text\", text: \"\", styles: {} }],\r\n children: [],\r\n };\r\n }\r\n\r\n /**\r\n * 콘텐츠 유효성 검증 및 기본값 설정\r\n * @param content 사용자 제공 콘텐츠 (객체 배열 또는 JSON 문자열)\r\n * @param emptyBlockCount 빈 블록 개수 (기본값: 3)\r\n * @returns 검증된 콘텐츠 배열\r\n */\r\n static validateContent(\r\n content?: DefaultPartialBlock[] | string,\r\n emptyBlockCount: number = 3\r\n ): DefaultPartialBlock[] {\r\n // 1. 문자열인 경우 JSON 파싱 시도\r\n if (typeof content === \"string\") {\r\n if (content.trim() === \"\") {\r\n return this.createEmptyBlocks(emptyBlockCount);\r\n }\r\n\r\n const parsedContent = this.parseJSONContent(content);\r\n if (parsedContent && parsedContent.length > 0) {\r\n return parsedContent;\r\n }\r\n\r\n // 파싱 실패 시 빈 블록 생성\r\n return this.createEmptyBlocks(emptyBlockCount);\r\n }\r\n\r\n // 2. 배열인 경우 기존 로직\r\n if (!content || content.length === 0) {\r\n return this.createEmptyBlocks(emptyBlockCount);\r\n }\r\n\r\n return content;\r\n }\r\n\r\n /**\r\n * 빈 블록들을 생성합니다\r\n * @param emptyBlockCount 생성할 블록 개수\r\n * @returns 생성된 빈 블록 배열\r\n */\r\n private static createEmptyBlocks(\r\n emptyBlockCount: number\r\n ): DefaultPartialBlock[] {\r\n return Array.from({ length: emptyBlockCount }, () =>\r\n this.createDefaultBlock()\r\n );\r\n }\r\n}\r\n\r\n/**\r\n * 에디터 설정 관리 유틸리티\r\n * 각종 설정의 기본값과 검증 로직을 담당\r\n */\r\nexport class EditorConfig {\r\n /**\r\n * 테이블 설정 기본값 적용\r\n * @param userTables 사용자 테이블 설정\r\n * @returns 기본값이 적용된 테이블 설정\r\n */\r\n static getDefaultTableConfig(userTables?: LumirEditorProps[\"tables\"]) {\r\n return {\r\n splitCells: userTables?.splitCells ?? true,\r\n cellBackgroundColor: userTables?.cellBackgroundColor ?? true,\r\n cellTextColor: userTables?.cellTextColor ?? true,\r\n headers: userTables?.headers ?? true,\r\n };\r\n }\r\n\r\n /**\r\n * 헤딩 설정 기본값 적용\r\n * @param userHeading 사용자 헤딩 설정\r\n * @returns 기본값이 적용된 헤딩 설정\r\n */\r\n static getDefaultHeadingConfig(userHeading?: LumirEditorProps[\"heading\"]) {\r\n return userHeading?.levels && userHeading.levels.length > 0\r\n ? userHeading\r\n : { levels: [1, 2, 3, 4, 5, 6] as (1 | 2 | 3 | 4 | 5 | 6)[] };\r\n }\r\n\r\n /**\r\n * 비활성화할 확장 기능 목록 생성\r\n * @param userExtensions 사용자 정의 비활성 확장\r\n * @param allowVideo 비디오 업로드 허용 여부\r\n * @param allowAudio 오디오 업로드 허용 여부\r\n * @param allowFile 일반 파일 업로드 허용 여부\r\n * @returns 비활성화할 확장 기능 목록\r\n */\r\n static getDisabledExtensions(\r\n userExtensions?: string[],\r\n allowVideo = false,\r\n allowAudio = false,\r\n allowFile = false\r\n ): string[] {\r\n const set = new Set<string>(userExtensions ?? []);\r\n if (!allowVideo) set.add(\"video\");\r\n if (!allowAudio) set.add(\"audio\");\r\n if (!allowFile) set.add(\"file\");\r\n return Array.from(set);\r\n }\r\n}\r\n\r\n// 파일 타입 검증 함수\r\n/** @internal 테스트용 export */\r\nexport const isImageFile = (file: File, maxSize?: number): boolean => {\r\n const limit = maxSize !== undefined ? maxSize : MAX_FILE_SIZE;\r\n if (file.size === 0 || file.size > limit) {\r\n return false;\r\n }\r\n\r\n // 🔒 보안: SVG 파일 차단 (XSS 방지)\r\n const fileName = file.name?.toLowerCase() || \"\";\r\n if (\r\n file.type === \"image/svg+xml\" ||\r\n BLOCKED_EXTENSIONS.some((ext) => fileName.endsWith(ext))\r\n ) {\r\n return false;\r\n }\r\n\r\n // 이미지 타입 검증\r\n return (\r\n file.type?.startsWith(\"image/\") ||\r\n (!file.type && /\\.(png|jpe?g|gif|webp|bmp)$/i.test(fileName))\r\n );\r\n};\r\n\r\n/** @internal 테스트용 export */\r\nexport const isVideoFile = (file: File, maxSize?: number): boolean => {\r\n const limit = maxSize !== undefined ? maxSize : MAX_VIDEO_FILE_SIZE;\r\n const sizeOk = file.size > 0 && file.size <= limit;\r\n const fileName = file.name?.toLowerCase() || \"\";\r\n const mimeMatch = ALLOWED_VIDEO_MIME_TYPES.has(file.type);\r\n const videoPrefix = typeof file.type === \"string\" && file.type.startsWith(\"video/\");\r\n const extMatch = !file.type && ALLOWED_VIDEO_EXTENSIONS.some((ext) => fileName.endsWith(ext));\r\n const result = sizeOk && (mimeMatch || videoPrefix || extMatch);\r\n // #region agent log\r\n DEBUG_LOG(\"isVideoFile:check\", \"result\", {\r\n fileName: file.name,\r\n fileType: file.type,\r\n fileSize: file.size,\r\n sizeOk,\r\n mimeMatch,\r\n videoPrefix,\r\n extMatch,\r\n result,\r\n });\r\n // #endregion\r\n return result;\r\n};\r\n\r\n/**\r\n * floatingMenu(상단 고정 툴바) 사용 시 팝업 포매팅 툴바를 숨길 선택인지 판별.\r\n *\r\n * - 일반 텍스트 선택 → true (상단 툴바가 모든 텍스트 도구를 제공하므로 팝업 중복.\r\n * 또한 상단 툴바 상호작용 직후 팝업이 잘못된 위치(0,0)로 재표시되는 문제 방지)\r\n * - 테이블 셀 컨텍스트(CellSelection·셀 내부 커서) → false\r\n * (셀 병합·세로 정렬·셀 배경 등 상단 툴바에 없는 도구가 팝업에만 있음)\r\n * - NodeSelection(이미지/비디오 등) → false (캡션·교체·다운로드 등 파일 도구 유지)\r\n *\r\n * @internal 테스트용 export\r\n */\r\nexport const isSuppressiblePopupSelection = (\r\n editor: any,\r\n selection: any\r\n): boolean => {\r\n if (!selection) return false;\r\n if (selection.node) return false; // NodeSelection\r\n if (isInTableCell(editor)) return false; // CellSelection 또는 셀 내부 커서\r\n return true;\r\n};\r\n\r\n/** @internal 테스트용 export */\r\nexport const isHtmlFile = (file: File): boolean => {\r\n return (\r\n file.size > 0 &&\r\n (file.type === \"text/html\" ||\r\n file.name?.toLowerCase().endsWith(\".html\") ||\r\n file.name?.toLowerCase().endsWith(\".htm\"))\r\n );\r\n};\r\n\r\n// ============================================\r\n// 🔒 보안 유틸리티 함수\r\n// ============================================\r\n\r\n/**\r\n * HTML 특수문자 이스케이프 (XSS 방지)\r\n * URL이나 사용자 입력을 HTML에 삽입할 때 사용\r\n * @internal 테스트용 export\r\n */\r\nexport const escapeHtml = (str: string): string => {\r\n const htmlEscapes: Record<string, string> = {\r\n \"&\": \"&\",\r\n \"<\": \"<\",\r\n \">\": \">\",\r\n '\"': \""\",\r\n \"'\": \"'\",\r\n };\r\n return str.replace(/[&<>\"']/g, (char) => htmlEscapes[char]);\r\n};\r\n\r\n/**\r\n * 블록 배열에서 모든 이미지 URL 추출\r\n * (중첩된 children도 재귀적으로 탐색)\r\n * @internal 테스트용 export\r\n */\r\nexport const extractImageUrls = (blocks: DefaultPartialBlock[]): Set<string> => {\r\n const urls = new Set<string>();\r\n\r\n const traverse = (blockList: DefaultPartialBlock[]) => {\r\n for (const block of blockList) {\r\n // image 블록에서 URL 추출\r\n if (block.type === \"image\" && (block.props as any)?.url) {\r\n const url = (block.props as any).url;\r\n if (typeof url === \"string\" && url.trim()) {\r\n urls.add(url);\r\n }\r\n }\r\n // children이 있으면 재귀 탐색\r\n if (block.children && Array.isArray(block.children)) {\r\n traverse(block.children as DefaultPartialBlock[]);\r\n }\r\n }\r\n };\r\n\r\n traverse(blocks);\r\n return urls;\r\n};\r\n\r\n/**\r\n * 블록 배열에서 이미지·비디오(미디어) URL 추출\r\n * (중첩된 children도 재귀적으로 탐색, onImageDelete 삭제 감지용)\r\n * @internal 테스트용 export\r\n */\r\nexport const extractMediaUrls = (blocks: DefaultPartialBlock[]): Set<string> => {\r\n const urls = new Set<string>();\r\n\r\n const traverse = (blockList: DefaultPartialBlock[]) => {\r\n for (const block of blockList) {\r\n if (block.type === \"image\" && (block.props as any)?.url) {\r\n const url = (block.props as any).url;\r\n if (typeof url === \"string\" && url.trim()) urls.add(url);\r\n }\r\n if (block.type === \"video\" && (block.props as any)?.url) {\r\n const url = (block.props as any).url;\r\n if (typeof url === \"string\" && url.trim()) urls.add(url);\r\n }\r\n if (block.children && Array.isArray(block.children)) {\r\n traverse(block.children as DefaultPartialBlock[]);\r\n }\r\n }\r\n };\r\n\r\n traverse(blocks);\r\n return urls;\r\n};\r\n\r\n/**\r\n * 삭제된 미디어(이미지·비디오) URL 찾기\r\n * (이전 블록에는 있었지만 현재 블록에는 없는 URL)\r\n * @internal 테스트용 export\r\n */\r\nexport const findDeletedMediaUrls = (\r\n previousUrls: Set<string>,\r\n currentUrls: Set<string>\r\n): string[] => {\r\n const deleted: string[] = [];\r\n previousUrls.forEach((url) => {\r\n if (!currentUrls.has(url)) deleted.push(url);\r\n });\r\n return deleted;\r\n};\r\n\r\n/**\r\n * 삭제된 이미지 URL 찾기 (findDeletedMediaUrls와 동일 로직, 하위 호환용)\r\n * @internal 테스트용 export\r\n */\r\nexport const findDeletedImageUrls = (\r\n previousUrls: Set<string>,\r\n currentUrls: Set<string>\r\n): string[] => findDeletedMediaUrls(previousUrls, currentUrls);\r\n\r\nconst findBlockWithLink = (blocks: any[], targetUrl: string): any | null => {\r\n for (const block of blocks) {\r\n if (block.content) {\r\n for (const item of block.content) {\r\n if (item.type === \"link\" && item.href === targetUrl) return block;\r\n if (item.content) {\r\n for (const sub of item.content) {\r\n if (sub.type === \"link\" && sub.href === targetUrl) return block;\r\n }\r\n }\r\n }\r\n }\r\n if (block.children?.length) {\r\n const found = findBlockWithLink(block.children, targetUrl);\r\n if (found) return found;\r\n }\r\n }\r\n return null;\r\n};\r\n\r\nconst ConvertToPreviewButton = ({ url }: { url: string }) => {\r\n const editor = useBlockNoteEditor();\r\n const Components = useComponentsContext()!;\r\n\r\n return (\r\n <Components.LinkToolbar.Button\r\n className=\"bn-button\"\r\n mainTooltip=\"링크 프리뷰로 전환\"\r\n label=\"링크 프리뷰로 전환\"\r\n isSelected={false}\r\n onClick={() => {\r\n try {\r\n const allBlocks = (editor as any).document;\r\n const targetBlock = findBlockWithLink(allBlocks, url)\r\n || editor.getTextCursorPosition().block;\r\n (editor as any).replaceBlocks(\r\n [targetBlock],\r\n [{ type: \"linkPreview\", props: { url } }]\r\n );\r\n } catch (err) {\r\n console.error(\"Convert to link preview failed:\", err);\r\n }\r\n }}\r\n icon={\r\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\r\n <rect x=\"1\" y=\"3\" width=\"14\" height=\"10\" rx=\"2\" stroke=\"currentColor\" strokeWidth=\"1.5\" fill=\"none\" />\r\n <line x1=\"1\" y1=\"9\" x2=\"15\" y2=\"9\" stroke=\"currentColor\" strokeWidth=\"1.5\" />\r\n <circle cx=\"5\" cy=\"6.5\" r=\"1.5\" stroke=\"currentColor\" strokeWidth=\"1\" fill=\"none\" />\r\n </svg>\r\n }\r\n />\r\n );\r\n};\r\n\r\nconst CustomLinkToolbar = (props: any) => {\r\n const editor = useBlockNoteEditor();\r\n const Components = useComponentsContext()!;\r\n const hasLinkPreview = !!(editor as any)?._linkPreviewApiEndpoint;\r\n\r\n return (\r\n <Components.LinkToolbar.Root\r\n className=\"bn-toolbar bn-link-toolbar\"\r\n onMouseEnter={props.stopHideTimer}\r\n onMouseLeave={props.startHideTimer}\r\n >\r\n <EditLinkButton url={props.url} text={props.text} editLink={props.editLink} />\r\n <OpenLinkButton url={props.url} />\r\n <DeleteLinkButton deleteLink={props.deleteLink} />\r\n {hasLinkPreview && (\r\n <ConvertToPreviewButton url={props.url} />\r\n )}\r\n </Components.LinkToolbar.Root>\r\n );\r\n};\r\n\r\nexport default function LumirEditor({\r\n // editor options\r\n initialContent,\r\n initialEmptyBlocks = 3,\r\n uploadFile,\r\n s3Upload,\r\n tables,\r\n heading,\r\n defaultStyles = true,\r\n disableExtensions,\r\n tabBehavior = \"prefer-navigate-ui\",\r\n trailingBlock = true,\r\n allowVideoUpload = false,\r\n allowAudioUpload = false,\r\n allowFileUpload = false,\r\n maxImageFileSize,\r\n maxVideoFileSize,\r\n // link preview\r\n linkPreview,\r\n // view options\r\n editable = true,\r\n theme = \"light\",\r\n formattingToolbar = true,\r\n linkToolbar = true,\r\n sideMenu = true,\r\n emojiPicker = true,\r\n filePanel = true,\r\n tableHandles = true,\r\n onSelectionChange,\r\n className = \"\",\r\n placeholder,\r\n sideMenuAddButton = false,\r\n columnDivider = false,\r\n floatingMenu = false,\r\n floatingMenuPosition = \"sticky\",\r\n // callbacks / refs\r\n onContentChange,\r\n onError,\r\n onImageDelete,\r\n}: LumirEditorProps) {\r\n // 이미지 업로드 로딩 상태\r\n const [isUploading, setIsUploading] = useState(false);\r\n /** S3 업로드 진행률 0–100. null이면 진행률 미표시 */\r\n const [uploadProgress, setUploadProgress] = useState<number | null>(null);\r\n // 에러 상태 (사용자에게 표시할 에러 메시지)\r\n const [errorMessage, setErrorMessage] = useState<string | null>(null);\r\n // FloatingMenu: DOM에 항상 있는 file input (onchange 미발생 방지)\r\n const floatingMenuFileInputRef = useRef<HTMLInputElement | null>(null);\r\n const floatingMenuBlockRef = useRef<{ id: string } | null>(null);\r\n const floatingMenuUploadStartTimeRef = useRef<number>(0);\r\n\r\n // 에러 처리 핸들러\r\n const handleError = useCallback(\r\n (error: LumirEditorError) => {\r\n // 콜백이 있으면 호출\r\n onError?.(error);\r\n // 사용자에게 에러 메시지 표시\r\n setErrorMessage(error.getUserMessage());\r\n // 3초 후 에러 메시지 자동 숨김\r\n setTimeout(() => setErrorMessage(null), 3000);\r\n },\r\n [onError]\r\n );\r\n const validatedContent = useMemo<DefaultPartialBlock[]>(() => {\r\n // 형제 키 fontSize → styles.fontSize 복원 (직렬화 호환 레이어)\r\n return liftFontSize(\r\n ContentUtils.validateContent(initialContent, initialEmptyBlocks)\r\n );\r\n }, [initialContent, initialEmptyBlocks]);\r\n\r\n // 테이블 설정 메모이제이션\r\n const tableConfig = useMemo(() => {\r\n return EditorConfig.getDefaultTableConfig(tables);\r\n }, [\r\n tables?.splitCells,\r\n tables?.cellBackgroundColor,\r\n tables?.cellTextColor,\r\n tables?.headers,\r\n ]);\r\n\r\n // 헤딩 설정 메모이제이션\r\n const headingConfig = useMemo(() => {\r\n return EditorConfig.getDefaultHeadingConfig(heading);\r\n }, [heading?.levels?.join(\",\") ?? \"\"]);\r\n\r\n // 비활성화 확장 메모이제이션\r\n const disabledExtensions = useMemo(() => {\r\n return EditorConfig.getDisabledExtensions(\r\n disableExtensions,\r\n allowVideoUpload,\r\n allowAudioUpload,\r\n allowFileUpload\r\n );\r\n }, [disableExtensions, allowVideoUpload, allowAudioUpload, allowFileUpload]);\r\n\r\n // #region agent log\r\n useEffect(() => {\r\n DEBUG_LOG(\"LumirEditor:init:disabledExtensions\", \"snapshot\", {\r\n allowVideoUpload,\r\n hasVideoInDisabled: disabledExtensions.includes(\"video\"),\r\n disabledList: disabledExtensions.slice(0, 15),\r\n });\r\n }, [allowVideoUpload, disabledExtensions]);\r\n // #endregion\r\n\r\n // fileNameTransform 콜백을 ref로 관리 (에디터 재생성 방지)\r\n const fileNameTransformRef = useRef(s3Upload?.fileNameTransform);\r\n useEffect(() => {\r\n fileNameTransformRef.current = s3Upload?.fileNameTransform;\r\n }, [s3Upload?.fileNameTransform]);\r\n\r\n // S3 업로드 설정 메모이제이션 (객체 참조 안정화)\r\n // 주의: fileNameTransform은 ref로 관리하므로 의존성에서 제외\r\n const memoizedS3Upload = useMemo(() => {\r\n if (!s3Upload) return undefined;\r\n return {\r\n apiEndpoint: s3Upload.apiEndpoint,\r\n env: s3Upload.env,\r\n path: s3Upload.path,\r\n appendUUID: s3Upload.appendUUID,\r\n preserveExtension: s3Upload.preserveExtension,\r\n uploadTimeoutMs: s3Upload.uploadTimeoutMs,\r\n maxRetries: s3Upload.maxRetries,\r\n onProgress: (percent: number) => {\r\n setUploadProgress(percent);\r\n s3Upload.onProgress?.(percent);\r\n },\r\n // 최신 콜백을 항상 사용하도록 ref를 통해 접근\r\n fileNameTransform: ((originalName: string, file: File) => {\r\n return fileNameTransformRef.current\r\n ? fileNameTransformRef.current(originalName, file)\r\n : originalName;\r\n }) as ((originalName: string, file: File) => string) | undefined,\r\n };\r\n }, [\r\n s3Upload?.apiEndpoint,\r\n s3Upload?.env,\r\n s3Upload?.path,\r\n s3Upload?.appendUUID,\r\n s3Upload?.preserveExtension,\r\n s3Upload?.uploadTimeoutMs,\r\n s3Upload?.maxRetries,\r\n s3Upload?.onProgress,\r\n ]);\r\n\r\n const editor = useCreateBlockNote(\r\n {\r\n // HTML 미리보기 블록이 포함된 커스텀 스키마 사용\r\n schema,\r\n // 모든 BlockNote UI 텍스트(테이블 드롭다운 등) 한글 적용 + \"색깔\"→\"색\"\r\n dictionary: {\r\n ...ko,\r\n drag_handle: { ...ko.drag_handle, colors_menuitem: \"색\" },\r\n formatting_toolbar: {\r\n ...ko.formatting_toolbar,\r\n colors: { ...ko.formatting_toolbar.colors, tooltip: \"색\" },\r\n },\r\n },\r\n initialContent: validatedContent as any,\r\n tables: tableConfig,\r\n heading: headingConfig,\r\n animations: false, // 기본적으로 애니메이션 비활성화\r\n defaultStyles,\r\n // 확장 비활성: 비디오/오디오/파일 제어\r\n disableExtensions: disabledExtensions,\r\n _tiptapOptions: {\r\n extensions: [\r\n VerticalAlignmentExtension,\r\n // 행 높이 attr 등록은 항상(저장된 높이 렌더), 드래그 리사이즈 UI는\r\n // tableHandles prop으로 게이트(기존 grip 컨트롤러와 동일 게이트).\r\n RowHeightExtension.configure({ resizable: tableHandles }),\r\n // 표 블록 정렬(좌/가운데/우) attr.\r\n TableAlignmentExtension,\r\n // 셀 포커스 시 Ctrl/Cmd+A → 표 전체 선택.\r\n TableSelectAllExtension,\r\n ],\r\n },\r\n placeholders: placeholder\r\n ? { default: placeholder, emptyDocument: placeholder }\r\n : undefined,\r\n tabBehavior,\r\n trailingBlock,\r\n uploadFile: async (file) => {\r\n const allowedImage = isImageFile(file, maxImageFileSize);\r\n const allowedVideo =\r\n allowVideoUpload && isVideoFile(file, maxVideoFileSize);\r\n // #region agent log\r\n DEBUG_LOG(\"uploadFile:step1:entry\", \"editor uploadFile callback invoked\", {\r\n fileName: file.name,\r\n fileType: file.type,\r\n fileSize: file.size,\r\n allowVideoUpload,\r\n allowedImage,\r\n allowedVideo,\r\n });\r\n // #endregion\r\n if (!allowedImage && !allowedVideo) {\r\n const error = LumirEditorError.invalidFileType(\r\n file.name,\r\n allowVideoUpload\r\n );\r\n handleError(error);\r\n throw error;\r\n }\r\n\r\n try {\r\n setUploadProgress(0);\r\n let fileUrl: string;\r\n // #region agent log\r\n const branch = uploadFile ? \"custom\" : memoizedS3Upload?.apiEndpoint ? \"s3\" : \"none\";\r\n DEBUG_LOG(\"uploadFile:step2:branch\", \"upload path\", {\r\n branch,\r\n hasCustomUploadFile: !!uploadFile,\r\n hasS3ApiEndpoint: !!memoizedS3Upload?.apiEndpoint,\r\n });\r\n // #endregion\r\n // 1. 사용자 정의 uploadFile 우선\r\n if (uploadFile) {\r\n const t0 = Date.now();\r\n DEBUG_LOG(\"uploadFile:step3a:custom\", \"calling custom uploadFile\", { fileName: file.name });\r\n fileUrl = await uploadFile(file);\r\n DEBUG_LOG(\"uploadFile:step3a:done\", \"custom uploadFile returned\", { urlLen: fileUrl?.length, elapsedMs: Date.now() - t0 });\r\n }\r\n // 2. S3 업로드 (uploadFile 없을 때)\r\n else if (memoizedS3Upload?.apiEndpoint) {\r\n const t0 = Date.now();\r\n DEBUG_LOG(\"uploadFile:step3b:s3\", \"calling S3 uploader\", { fileName: file.name });\r\n const s3Uploader = createS3Uploader(memoizedS3Upload);\r\n fileUrl = await s3Uploader(file);\r\n DEBUG_LOG(\"uploadFile:step3b:done\", \"S3 uploader returned\", { urlLen: fileUrl?.length, elapsedMs: Date.now() - t0 });\r\n }\r\n // 3. 업로드 방법이 없으면 에러\r\n else {\r\n const error = LumirEditorError.s3ConfigError(\r\n \"No upload method available. Please provide uploadFile or s3Upload configuration.\"\r\n );\r\n handleError(error);\r\n throw error;\r\n }\r\n\r\n // BlockNote가 파일 타입에 따라 이미지/비디오 블록을 생성하도록 URL만 반환\r\n // #region agent log\r\n DEBUG_LOG(\"uploadFile:step4:success\", \"returning URL\", {\r\n fileName: file.name,\r\n urlPrefix: fileUrl.slice(0, 80),\r\n });\r\n // #endregion\r\n return fileUrl;\r\n } catch (error) {\r\n // #region agent log\r\n DEBUG_LOG(\"uploadFile:step5:catch\", \"uploadFile threw\", {\r\n fileName: file.name,\r\n errorMessage: error instanceof Error ? error.message : String(error),\r\n });\r\n // #endregion\r\n // 이미 LumirEditorError인 경우 다시 처리하지 않음\r\n if (error instanceof LumirEditorError) {\r\n throw error;\r\n }\r\n const lumirError = LumirEditorError.uploadFailed(\r\n error instanceof Error ? error.message : String(error),\r\n error instanceof Error ? error : undefined\r\n );\r\n handleError(lumirError);\r\n throw lumirError;\r\n } finally {\r\n setUploadProgress(null);\r\n }\r\n },\r\n pasteHandler: (ctx) => {\r\n const { event, editor, defaultPasteHandler } = ctx as any;\r\n\r\n // URL 붙여넣기 감지 → linkPreview 블록 생성\r\n if (linkPreview?.apiEndpoint) {\r\n const text = event?.clipboardData?.getData?.(\"text/plain\") || \"\";\r\n const trimmed = text.trim();\r\n if (\r\n trimmed &&\r\n /^https?:\\/\\/\\S+$/i.test(trimmed) &&\r\n !event?.clipboardData?.files?.length\r\n ) {\r\n event.preventDefault();\r\n const currentBlock = editor.getTextCursorPosition().block;\r\n const blockText = currentBlock.content\r\n ?.map((c: any) => c.text || \"\")\r\n .join(\"\")\r\n .trim();\r\n if (!blockText && currentBlock.type === \"paragraph\") {\r\n editor.updateBlock(currentBlock, {\r\n type: \"linkPreview\",\r\n props: { url: trimmed },\r\n });\r\n } else {\r\n editor.insertBlocks(\r\n [{ type: \"linkPreview\", props: { url: trimmed } }],\r\n currentBlock,\r\n \"after\"\r\n );\r\n }\r\n return true;\r\n }\r\n }\r\n\r\n // 엑셀/스프레드시트 표 붙여넣기 감지\r\n // 엑셀은 클립보드에 표 HTML과 함께 셀 비트맵(image.png)을 올리므로,\r\n // 아래 이미지 업로드 분기가 가로채기 전에 HTML 표를 우선 처리한다.\r\n // BlockNote 기본 HTML 파서가 병합/들쑥날쑥 행을 안전하게 표로 변환한다.\r\n const pastedHtml =\r\n event?.clipboardData?.getData?.(\"text/html\") || \"\";\r\n if (/<table[\\s>]/i.test(pastedHtml)) {\r\n // #region agent log\r\n DEBUG_LOG(\"paste:step0:table\", \"table HTML detected, using pasteHTML\", {\r\n htmlLen: pastedHtml.length,\r\n hasFiles: !!event?.clipboardData?.files?.length,\r\n });\r\n // #endregion\r\n event.preventDefault();\r\n // 에디터 너비보다 넓은 표는 비율 유지하며 축소(가로 스크롤 방지):\r\n // 붙여넣기 전에 HTML에서 열 너비를 읽어 maxWidth에 맞춘 columnWidths를 계산하고,\r\n // 붙여넣은 뒤 새 표 블록에 적용한다(파서가 셀 width를 colwidth로 읽지 않으므로).\r\n const pmDom = (editor as any).prosemirrorView?.dom as HTMLElement | undefined;\r\n const maxWidth = pmDom?.clientWidth ? pmDom.clientWidth - 8 : 0;\r\n const fittedWidths = computeFittedColumnWidthsPerTable(pastedHtml, maxWidth);\r\n const beforeTableIds = new Set<string>(\r\n collectTableBlocks(editor.document).map((b: any) => b.id),\r\n );\r\n // 엑셀 CSS 서식(색/정렬/굵게)을 BlockNote가 인식하는 형태로 정규화 후 삽입.\r\n editor.pasteHTML(normalizeExcelTableHtml(pastedHtml));\r\n applyFittedWidthsToNewTables(editor, beforeTableIds, fittedWidths);\r\n return true;\r\n }\r\n\r\n const fileList =\r\n (event?.clipboardData?.files as FileList | null) ?? null;\r\n const files: File[] = fileList ? Array.from(fileList) : [];\r\n const acceptedFiles: File[] = files.filter(\r\n (f) =>\r\n isImageFile(f, maxImageFileSize) || (allowVideoUpload && isVideoFile(f, maxVideoFileSize))\r\n );\r\n // #region agent log\r\n DEBUG_LOG(\"paste:step1:files\", \"paste clipboard files\", {\r\n filesCount: files.length,\r\n acceptedCount: acceptedFiles.length,\r\n fileNames: files.map((f) => f.name),\r\n acceptedNames: acceptedFiles.map((f) => f.name),\r\n });\r\n // #endregion\r\n // 파일이 있지만 허용된 미디어가 없으면 기본 처리 막고 무시\r\n if (files.length > 0 && acceptedFiles.length === 0) {\r\n event.preventDefault();\r\n return true;\r\n }\r\n\r\n if (acceptedFiles.length === 0) {\r\n return defaultPasteHandler() ?? false;\r\n }\r\n\r\n event.preventDefault();\r\n (async () => {\r\n setIsUploading(true);\r\n try {\r\n for (const file of acceptedFiles) {\r\n try {\r\n // #region agent log\r\n DEBUG_LOG(\"paste:step2:upload\", \"calling uploadFile for paste\", {\r\n fileName: file.name,\r\n fileType: file.type,\r\n });\r\n // #endregion\r\n const url = await editor.uploadFile(file);\r\n if (isImageFile(file, maxImageFileSize)) {\r\n editor.pasteHTML(\r\n `<img src=\"${escapeHtml(url)}\" alt=\"image\" />`\r\n );\r\n } else if (isVideoFile(file, maxVideoFileSize)) {\r\n const currentBlock = editor.getTextCursorPosition().block;\r\n editor.insertBlocks(\r\n [{ type: \"video\", props: { url } }] as any,\r\n currentBlock,\r\n \"after\"\r\n );\r\n }\r\n } catch (err) {\r\n console.warn(\r\n \"Upload failed, skipped:\",\r\n file.name || \"\",\r\n err\r\n );\r\n }\r\n }\r\n } finally {\r\n setIsUploading(false);\r\n }\r\n })();\r\n return true;\r\n },\r\n },\r\n [\r\n validatedContent,\r\n tableConfig,\r\n headingConfig,\r\n defaultStyles,\r\n disabledExtensions,\r\n tabBehavior,\r\n trailingBlock,\r\n uploadFile,\r\n memoizedS3Upload,\r\n allowVideoUpload,\r\n linkPreview?.apiEndpoint,\r\n placeholder,\r\n // tableHandles 변경 시 RowHeightExtension의 resizable 게이트가 반영되도록 재생성\r\n tableHandles,\r\n ]\r\n );\r\n\r\n // Link Preview API endpoint를 에디터 인스턴스에 동기적으로 연결\r\n // (useEffect 사용 시 블록 렌더링보다 늦게 설정되어 첫 fetch가 실패할 수 있음)\r\n if (editor && linkPreview?.apiEndpoint) {\r\n (editor as any)._linkPreviewApiEndpoint = linkPreview.apiEndpoint;\r\n }\r\n\r\n // columnDnd 플러그인이 드롭 시 BlockNote 블록 API(getBlock/replaceBlocks)에 접근할 수\r\n // 있도록, ProseMirror view에 에디터 참조를 심는다(플러그인은 view.__lumirEditor로 읽음).\r\n if (editor) {\r\n try {\r\n const view = (editor as any).prosemirrorView;\r\n if (view) {\r\n view.__lumirEditor = editor;\r\n }\r\n } catch {\r\n /* view 미준비 시 무시(다음 렌더에서 설정) */\r\n }\r\n }\r\n\r\n // 편집 가능 여부 설정\r\n useEffect(() => {\r\n if (editor) {\r\n editor.isEditable = editable;\r\n }\r\n }, [editor, editable]);\r\n\r\n // 행/열 삭제 결정적 처리 래퍼.\r\n // 코어의 tableHandles.removeRowOrColumn은 삭제 대상 표를 플러그인 내부 상태\r\n // view.tablePos로 찾는데, 포커스 기반 커스텀 그립 컨트롤러에서는 이 값이 어긋날 수\r\n // 있어 잘못된 CellSelection이 만들어지고 첫 열/행 삭제가 화면에 반영되지 않는다.\r\n // 현재 selection(커서)이 위치한 실제 표 기준으로 직접 삭제하고, 표 컨텍스트가\r\n // 아니면(=false) 코어 기본 동작으로 폴백한다.\r\n useEffect(() => {\r\n if (!editor) return;\r\n const th: any = (editor as any).tableHandles;\r\n if (!th || th.__lumirColDelPatched || typeof th.removeRowOrColumn !== \"function\")\r\n return;\r\n const orig = th.removeRowOrColumn.bind(th);\r\n th.removeRowOrColumn = (index: number, dir: \"row\" | \"column\") => {\r\n if (removeFocusedRowOrColumn(editor, index, dir)) return;\r\n return orig(index, dir);\r\n };\r\n th.__lumirColDelPatched = true;\r\n }, [editor]);\r\n\r\n // floatingMenu(상단 고정 툴바) 사용 시: 일반 텍스트 선택에서는 팝업 포매팅\r\n // 툴바를 띄우지 않는다 (중복 UI + 상단 툴바에서 스타일 적용 직후 팝업이\r\n // (0,0) 위치로 잘못 재표시되는 문제 방지). 테이블 셀·노드 선택은 팝업 유지.\r\n // show:true emit 직후 동기적으로 closeMenu()하면 React 배칭으로 화면에\r\n // 그려지기 전에 show:false로 정리된다.\r\n useEffect(() => {\r\n if (!editor || !floatingMenu) return;\r\n\r\n const ft = (editor as any).formattingToolbar;\r\n if (!ft?.onUpdate) return;\r\n\r\n const unsubscribe = ft.onUpdate((state: { show?: boolean }) => {\r\n if (!state?.show) return;\r\n const selection = (editor as any)._tiptapEditor?.state?.selection;\r\n if (isSuppressiblePopupSelection(editor, selection)) {\r\n ft.closeMenu();\r\n }\r\n });\r\n return unsubscribe;\r\n }, [editor, floatingMenu]);\r\n\r\n // 콘텐츠 변경 감지\r\n useEffect(() => {\r\n if (!editor || !onContentChange) return;\r\n\r\n const handleContentChange = () => {\r\n const blocks = editor.topLevelBlocks as DefaultPartialBlock[];\r\n // ⚠️ styles.fontSize는 구버전 SDK(fontSize 스펙 없음)를 크래시시키므로\r\n // 외부로 나가는 JSON은 반드시 lowerFontSize(형제 키 변환)를 거친다.\r\n // 표 커스텀 셀 attr(세로 정렬·행 높이)은 한 번의 순회로 함께 주입한다.\r\n const patched = lowerFontSize(\r\n injectTableBlockAttrs(\r\n injectTableCellAttrs(blocks, editor, [\r\n \"verticalAlignment\",\r\n \"rowHeight\",\r\n ]),\r\n editor\r\n )\r\n );\r\n onContentChange(patched);\r\n };\r\n\r\n return editor.onEditorContentChange(handleContentChange);\r\n }, [editor, onContentChange]);\r\n\r\n // 이미지·비디오 삭제 감지 (onImageDelete 콜백 — 미디어 URL 모두 전달)\r\n const previousMediaUrlsRef = useRef<Set<string>>(new Set());\r\n\r\n useEffect(() => {\r\n if (!editor) return;\r\n\r\n const initialBlocks = editor.topLevelBlocks as DefaultPartialBlock[];\r\n previousMediaUrlsRef.current = extractMediaUrls(initialBlocks);\r\n }, [editor]);\r\n\r\n useEffect(() => {\r\n if (!editor || !onImageDelete) return;\r\n\r\n const handleMediaDeleteCheck = () => {\r\n const currentBlocks = editor.topLevelBlocks as DefaultPartialBlock[];\r\n const currentUrls = extractMediaUrls(currentBlocks);\r\n const previousUrls = previousMediaUrlsRef.current;\r\n\r\n const deletedUrls = findDeletedMediaUrls(previousUrls, currentUrls);\r\n deletedUrls.forEach((url) => {\r\n onImageDelete(url);\r\n });\r\n\r\n previousMediaUrlsRef.current = currentUrls;\r\n };\r\n\r\n return editor.onEditorContentChange(handleMediaDeleteCheck);\r\n }, [editor, onImageDelete]);\r\n\r\n // 드래그앤드롭 이미지/HTML 처리\r\n useEffect(() => {\r\n const el = editor?.domElement as HTMLElement | undefined;\r\n if (!el) return;\r\n\r\n const handleDragOver = (e: DragEvent) => {\r\n if (e.defaultPrevented) return;\r\n const hasFiles = (\r\n e.dataTransfer?.types as unknown as string[] | undefined\r\n )?.includes?.(\"Files\");\r\n if (hasFiles) {\r\n e.preventDefault();\r\n e.stopPropagation();\r\n }\r\n };\r\n\r\n const handleDrop = (e: DragEvent) => {\r\n if (!e.dataTransfer) return;\r\n const hasFiles = (\r\n (e.dataTransfer.types as unknown as string[] | undefined) ?? []\r\n ).includes(\"Files\");\r\n if (!hasFiles) return;\r\n\r\n e.preventDefault();\r\n e.stopPropagation();\r\n\r\n const items = Array.from(e.dataTransfer.items ?? []);\r\n const files = items\r\n .filter((it) => it.kind === \"file\")\r\n .map((it) => it.getAsFile())\r\n .filter((f): f is File => !!f);\r\n\r\n // 이미지, 동영상, HTML 파일 분리\r\n const imageFiles = files.filter((f) => isImageFile(f, maxImageFileSize));\r\n const videoFiles = allowVideoUpload\r\n ? files.filter((f) => isVideoFile(f, maxVideoFileSize))\r\n : [];\r\n const htmlFiles = files.filter(isHtmlFile);\r\n\r\n // #region agent log\r\n DEBUG_LOG(\"drop:step1:files\", \"drop received\", {\r\n filesCount: files.length,\r\n imageCount: imageFiles.length,\r\n videoCount: videoFiles.length,\r\n htmlCount: htmlFiles.length,\r\n allowVideoUpload,\r\n firstFile: files[0]\r\n ? {\r\n name: files[0].name,\r\n type: files[0].type,\r\n size: files[0].size,\r\n isImage: isImageFile(files[0], maxImageFileSize),\r\n isVideo: isVideoFile(files[0], maxVideoFileSize),\r\n }\r\n : null,\r\n });\r\n // #endregion\r\n\r\n if (\r\n imageFiles.length === 0 &&\r\n htmlFiles.length === 0 &&\r\n videoFiles.length === 0\r\n )\r\n return;\r\n\r\n (async () => {\r\n setIsUploading(true);\r\n try {\r\n // #region agent log\r\n DEBUG_LOG(\"drop:step2:async\", \"drop async started\", {\r\n imageCount: imageFiles.length,\r\n videoCount: videoFiles.length,\r\n });\r\n // #endregion\r\n // 이미지 파일 처리\r\n for (const file of imageFiles) {\r\n try {\r\n if (editor?.uploadFile) {\r\n const url = await editor.uploadFile(file);\r\n if (url && typeof url === \"string\") {\r\n editor.pasteHTML(\r\n `<img src=\"${escapeHtml(url)}\" alt=\"image\" />`\r\n );\r\n }\r\n }\r\n } catch (err) {\r\n console.warn(\r\n \"Image upload failed, skipped:\",\r\n file.name || \"\",\r\n err\r\n );\r\n }\r\n }\r\n\r\n // 동영상 파일 처리 - video 블록으로 삽입\r\n // #region agent log\r\n DEBUG_LOG(\"drop:step3:videoLoop\", \"video loop start\", {\r\n videoCount: videoFiles.length,\r\n names: videoFiles.map((f) => f.name),\r\n });\r\n // #endregion\r\n for (const file of videoFiles) {\r\n try {\r\n if (editor?.uploadFile) {\r\n // #region agent log\r\n DEBUG_LOG(\"drop:step4:videoUpload\", \"calling uploadFile for video\", {\r\n fileName: file.name,\r\n });\r\n // #endregion\r\n const url = await editor.uploadFile(file);\r\n if (url && typeof url === \"string\") {\r\n const currentBlock = editor.getTextCursorPosition().block;\r\n editor.insertBlocks(\r\n [{ type: \"video\", props: { url } }] as any,\r\n currentBlock,\r\n \"after\"\r\n );\r\n }\r\n }\r\n } catch (err) {\r\n console.warn(\r\n \"Video upload failed, skipped:\",\r\n file.name || \"\",\r\n err\r\n );\r\n }\r\n }\r\n\r\n // HTML 파일 처리 - htmlPreview 블록으로 삽입\r\n for (const file of htmlFiles) {\r\n try {\r\n const htmlContent = await file.text();\r\n const currentBlock = editor.getTextCursorPosition().block;\r\n\r\n // htmlPreview 블록 삽입\r\n editor.insertBlocks(\r\n [\r\n {\r\n type: \"htmlPreview\",\r\n props: {\r\n htmlContent: htmlContent,\r\n fileName: file.name,\r\n height: \"400px\",\r\n },\r\n },\r\n ],\r\n currentBlock,\r\n \"after\"\r\n );\r\n } catch (err) {\r\n console.warn(\r\n \"HTML file processing failed, skipped:\",\r\n file.name || \"\",\r\n err\r\n );\r\n }\r\n }\r\n } finally {\r\n setIsUploading(false);\r\n }\r\n })();\r\n };\r\n\r\n el.addEventListener(\"dragover\", handleDragOver, { capture: true });\r\n el.addEventListener(\"drop\", handleDrop, { capture: true });\r\n\r\n return () => {\r\n el.removeEventListener(\"dragover\", handleDragOver, {\r\n capture: true,\r\n } as any);\r\n el.removeEventListener(\"drop\", handleDrop, { capture: true } as any);\r\n };\r\n }, [editor, allowVideoUpload]);\r\n\r\n // SideMenu 설정 (Add 버튼 제어)\r\n const computedSideMenu = useMemo(() => {\r\n return sideMenuAddButton ? sideMenu : false;\r\n }, [sideMenuAddButton, sideMenu]);\r\n\r\n // Add 버튼 없는 사이드 메뉴 (드래그 핸들만) - 메모이제이션\r\n // 드래그 핸들 드롭다운에 표 정렬(좌/가운데/우) 항목을 추가한 커스텀 메뉴 사용.\r\n const DragHandleOnlySideMenu = useMemo(() => {\r\n return (props: any) => (\r\n <BlockSideMenu {...props}>\r\n <DragHandleButton {...props} dragHandleMenu={LumirDragHandleMenu} />\r\n </BlockSideMenu>\r\n );\r\n }, []);\r\n\r\n return (\r\n <div\r\n className={cn(\r\n \"lumirEditor\",\r\n columnDivider && \"lumir-column-divider\",\r\n className,\r\n )}\r\n style={{ position: \"relative\", display: \"flex\", flexDirection: \"column\" }}\r\n >\r\n {/* FloatingMenu를 BlockNoteView 외부로 이동 */}\r\n {floatingMenu && editor && (\r\n <>\r\n <input\r\n ref={floatingMenuFileInputRef}\r\n type=\"file\"\r\n accept={\r\n allowVideoUpload\r\n ? \"image/*,video/mp4,video/webm,video/ogg,video/quicktime,.mov\"\r\n : \"image/*\"\r\n }\r\n style={{\r\n position: \"absolute\",\r\n left: \"-9999px\",\r\n opacity: 0,\r\n pointerEvents: \"none\",\r\n }}\r\n onChange={async (e) => {\r\n const inputEl = e.target as HTMLInputElement;\r\n const file = inputEl.files?.[0];\r\n // #region agent log\r\n DEBUG_LOG(\"FloatingMenu:step3:onchange\", \"file input onchange fired\", {\r\n hasFile: !!file,\r\n fileName: file?.name,\r\n fileType: file?.type,\r\n fileSize: file?.size,\r\n hasUploadFile: !!editor?.uploadFile,\r\n });\r\n // #endregion\r\n const blockToInsertAfter = floatingMenuBlockRef.current;\r\n if (file && editor.uploadFile && blockToInsertAfter) {\r\n const allowedImage = isImageFile(file, maxImageFileSize);\r\n const allowedVideo = allowVideoUpload && isVideoFile(file, maxVideoFileSize);\r\n // #region agent log\r\n DEBUG_LOG(\"FloatingMenu:step4:fileCheck\", \"allowed check\", {\r\n fileName: file.name,\r\n allowedImage,\r\n allowedVideo,\r\n });\r\n // #endregion\r\n if (allowedImage || allowedVideo) {\r\n try {\r\n setIsUploading(true);\r\n floatingMenuUploadStartTimeRef.current = Date.now();\r\n // #region agent log\r\n DEBUG_LOG(\"FloatingMenu:step5:uploadStart\", \"calling editor.uploadFile\", {\r\n fileName: file.name,\r\n });\r\n // #endregion\r\n const url = await editor.uploadFile(file);\r\n const blockType = allowedVideo ? \"video\" : \"image\";\r\n const elapsedMs = Date.now() - floatingMenuUploadStartTimeRef.current;\r\n // #region agent log\r\n DEBUG_LOG(\"FloatingMenu:step6:uploadDone\", \"upload returned, inserting block\", {\r\n blockType,\r\n blockId: blockToInsertAfter.id,\r\n urlLen: url?.length,\r\n elapsedMs,\r\n });\r\n // #endregion\r\n editor.insertBlocks(\r\n [\r\n {\r\n type: blockType,\r\n props: { url: url as string },\r\n },\r\n ] as any,\r\n blockToInsertAfter,\r\n \"after\"\r\n );\r\n } catch (err) {\r\n // #region agent log\r\n DEBUG_LOG(\"FloatingMenu:step7:catch\", \"upload or insert failed\", {\r\n errMsg: err instanceof Error ? err.message : String(err),\r\n });\r\n // #endregion\r\n console.error(\"Upload failed:\", err);\r\n } finally {\r\n setIsUploading(false);\r\n }\r\n }\r\n }\r\n inputEl.value = \"\";\r\n }}\r\n />\r\n <FloatingMenu\r\n editor={editor as any}\r\n position={floatingMenuPosition}\r\n onImageUpload={() => {\r\n // #region agent log\r\n DEBUG_LOG(\"FloatingMenu:step1:click\", \"upload button clicked\", {\r\n allowVideoUpload,\r\n });\r\n // #endregion\r\n let blockToInsertAfter: { id: string };\r\n try {\r\n blockToInsertAfter = editor.getTextCursorPosition().block;\r\n } catch (err) {\r\n DEBUG_LOG(\"FloatingMenu:step1b:error\", \"getTextCursorPosition failed\", {\r\n err: err instanceof Error ? err.message : String(err),\r\n });\r\n return;\r\n }\r\n floatingMenuBlockRef.current = blockToInsertAfter;\r\n const input = floatingMenuFileInputRef.current;\r\n if (!input) return;\r\n input.accept = allowVideoUpload\r\n ? \"image/*,video/mp4,video/webm,video/ogg,video/quicktime,.mov\"\r\n : \"image/*\";\r\n input.value = \"\";\r\n // #region agent log\r\n DEBUG_LOG(\"FloatingMenu:step2:inputReady\", \"persistent input ref, about to click\", {\r\n accept: input.accept,\r\n });\r\n DEBUG_LOG(\"FloatingMenu:step2b:click\", \"input.click() about to be called\", {});\r\n // #endregion\r\n input.click();\r\n }}\r\n />\r\n </>\r\n )}\r\n <BlockNoteView\r\n editor={editor}\r\n editable={editable}\r\n theme={theme}\r\n formattingToolbar={false}\r\n linkToolbar={false}\r\n sideMenu={computedSideMenu}\r\n slashMenu={false}\r\n emojiPicker={emojiPicker}\r\n filePanel={filePanel}\r\n tableHandles={false}\r\n onSelectionChange={onSelectionChange}\r\n >\r\n {/* Notion 스타일 focus 기반 테이블 grip (기본 hover 핸들 대체).\r\n 소비자 tableHandles prop을 게이트로 유지. */}\r\n {tableHandles && <LumirTableHandlesController />}\r\n {formattingToolbar && (\r\n <FormattingToolbarController\r\n formattingToolbar={CustomFormattingToolbar}\r\n />\r\n )}\r\n {linkToolbar && (\r\n linkPreview?.apiEndpoint\r\n ? <LinkToolbarController linkToolbar={CustomLinkToolbar} />\r\n : <LinkToolbarController />\r\n )}\r\n {\r\n <SuggestionMenuController\r\n triggerCharacter=\"/\"\r\n getItems={useCallback(\r\n async (query: string) => {\r\n const items = getDefaultReactSlashMenuItems(editor);\r\n // 오디오/파일 항목 제거; 비디오는 allowVideoUpload일 때만 표시\r\n const filtered = items.filter((item: any) => {\r\n const key = (item?.key || \"\").toString().toLowerCase();\r\n const title = (item?.title || \"\").toString().toLowerCase();\r\n if (key === \"video\" || title.includes(\"video\"))\r\n return allowVideoUpload;\r\n if ([\"audio\", \"file\"].includes(key)) return false;\r\n if (title.includes(\"audio\") || title.includes(\"file\"))\r\n return false;\r\n return true;\r\n });\r\n\r\n // HTML 미리보기 슬래시 메뉴 항목 추가\r\n const htmlPreviewItem = {\r\n title: \"HTML Preview\",\r\n onItemClick: () => {\r\n // 파일 선택 다이얼로그 열기\r\n const input = document.createElement(\"input\");\r\n input.type = \"file\";\r\n input.accept = \".html,.htm\";\r\n input.onchange = async (e) => {\r\n const file = (e.target as HTMLInputElement).files?.[0];\r\n if (file) {\r\n const htmlContent = await file.text();\r\n const currentBlock =\r\n editor.getTextCursorPosition().block;\r\n editor.insertBlocks(\r\n [\r\n {\r\n type: \"htmlPreview\",\r\n props: {\r\n htmlContent: htmlContent,\r\n fileName: file.name,\r\n height: \"400px\",\r\n },\r\n },\r\n ],\r\n currentBlock,\r\n \"after\"\r\n );\r\n }\r\n };\r\n input.click();\r\n },\r\n aliases: [\"html\", \"preview\", \"웹\", \"웹페이지\"],\r\n group: \"Embeds\",\r\n icon: (\r\n <svg\r\n width=\"18\"\r\n height=\"18\"\r\n viewBox=\"0 0 24 24\"\r\n fill=\"none\"\r\n stroke=\"currentColor\"\r\n strokeWidth=\"2\"\r\n strokeLinecap=\"round\"\r\n strokeLinejoin=\"round\"\r\n >\r\n <polyline points=\"16 18 22 12 16 6\"></polyline>\r\n <polyline points=\"8 6 2 12 8 18\"></polyline>\r\n </svg>\r\n ),\r\n subtext: \"HTML 파일을 미리보기로 삽입\",\r\n };\r\n\r\n // 2단 컬럼(다단 레이아웃) 슬래시 항목 — 생성 시 구분선 유무를 선택해 고정.\r\n // columnList/column은 public blockSchema에 없어 insertBlocks가 거부 →\r\n // PM 트랜잭션으로 직접 삽입(insertTwoColumns).\r\n const columnIcon = (withDivider: boolean) => (\r\n <svg\r\n width=\"18\"\r\n height=\"18\"\r\n viewBox=\"0 0 24 24\"\r\n fill=\"none\"\r\n stroke=\"currentColor\"\r\n strokeWidth=\"2\"\r\n strokeLinecap=\"round\"\r\n strokeLinejoin=\"round\"\r\n >\r\n <rect x=\"3\" y=\"4\" width=\"7\" height=\"16\" rx=\"1\" />\r\n <rect x=\"14\" y=\"4\" width=\"7\" height=\"16\" rx=\"1\" />\r\n {withDivider && (\r\n <line x1=\"12\" y1=\"3\" x2=\"12\" y2=\"21\" strokeDasharray=\"2 2\" />\r\n )}\r\n </svg>\r\n );\r\n const columnItem = {\r\n title: \"2단 컬럼\",\r\n onItemClick: () => insertTwoColumns(editor, false),\r\n aliases: [\"columns\", \"column\", \"2col\", \"단\", \"컬럼\", \"다단\", \"분할\"],\r\n group: \"Basic blocks\",\r\n icon: columnIcon(false),\r\n subtext: \"블록을 좌우 2단으로 배치\",\r\n };\r\n const columnDividerItem = {\r\n title: \"2단 컬럼 (구분선)\",\r\n onItemClick: () => insertTwoColumns(editor, true),\r\n aliases: [\r\n \"columns divider\",\r\n \"구분선\",\r\n \"컬럼 구분선\",\r\n \"2단 구분선\",\r\n \"divider\",\r\n ],\r\n group: \"Basic blocks\",\r\n icon: columnIcon(true),\r\n subtext: \"가운데 세로 구분선이 있는 2단 컬럼\",\r\n };\r\n\r\n const allItems = [\r\n ...filtered,\r\n htmlPreviewItem,\r\n columnItem,\r\n columnDividerItem,\r\n ];\r\n\r\n // Link Preview 슬래시 메뉴 항목 (linkPreview 설정이 있을 때만)\r\n if (linkPreview?.apiEndpoint) {\r\n allItems.push({\r\n title: \"Link Preview\",\r\n onItemClick: () => {\r\n insertOrUpdateBlock(editor, {\r\n type: \"linkPreview\",\r\n props: { url: \"\" },\r\n });\r\n },\r\n aliases: [\r\n \"link\",\r\n \"preview\",\r\n \"url\",\r\n \"링크\",\r\n \"미리보기\",\r\n \"프리뷰\",\r\n ],\r\n group: \"Embeds\",\r\n icon: (\r\n <svg\r\n width=\"18\"\r\n height=\"18\"\r\n viewBox=\"0 0 24 24\"\r\n fill=\"none\"\r\n stroke=\"currentColor\"\r\n strokeWidth=\"2\"\r\n strokeLinecap=\"round\"\r\n strokeLinejoin=\"round\"\r\n >\r\n <path d=\"M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71\" />\r\n <path d=\"M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71\" />\r\n </svg>\r\n ),\r\n subtext: \"URL의 미리보기 카드를 삽입\",\r\n });\r\n }\r\n\r\n // 한글 제목은 유지하되, 영어로도 검색 가능하도록 각 항목에 영어\r\n // 로케일(en)의 별칭 + 영어 제목을 별칭으로 병합한다(예: /table, /heading).\r\n const enSlash = en.slash_menu as Record<\r\n string,\r\n { title?: string; aliases?: string[] }\r\n >;\r\n for (const it of allItems as any[]) {\r\n const enEntry = it.key ? enSlash[it.key] : undefined;\r\n if (!enEntry) continue;\r\n const extra = [...(enEntry.aliases ?? []), enEntry.title]\r\n .filter((s): s is string => Boolean(s))\r\n .map((s) => s.toLowerCase());\r\n it.aliases = Array.from(\r\n new Set([...(it.aliases ?? []), ...extra])\r\n );\r\n }\r\n\r\n // 같은 group 항목이 비연속으로 섞여 있으면 SuggestionMenu가 동일\r\n // group 헤더를 중복 렌더해 React key 충돌이 난다(예: ko 로케일에서\r\n // \"제목\" group이 분리됨). group을 안정 정렬해 연속되도록 만든다.\r\n const groupOrder: string[] = [];\r\n for (const it of allItems) {\r\n const g = (it as any).group ?? \"\";\r\n if (!groupOrder.includes(g)) groupOrder.push(g);\r\n }\r\n allItems.sort(\r\n (a: any, b: any) =>\r\n groupOrder.indexOf(a.group ?? \"\") -\r\n groupOrder.indexOf(b.group ?? \"\")\r\n );\r\n\r\n if (!query) return allItems;\r\n const q = query.toLowerCase();\r\n return allItems.filter(\r\n (item: any) =>\r\n item.title?.toLowerCase().includes(q) ||\r\n (item.aliases || []).some((a: string) =>\r\n a.toLowerCase().includes(q)\r\n )\r\n );\r\n },\r\n [editor, allowVideoUpload, linkPreview?.apiEndpoint]\r\n )}\r\n />\r\n }\r\n {!sideMenuAddButton && (\r\n <SideMenuController sideMenu={DragHandleOnlySideMenu} />\r\n )}\r\n </BlockNoteView>\r\n\r\n {/* 이미지/비디오 업로드 로딩 스피너 */}\r\n {isUploading && (\r\n <div className=\"lumirEditor-upload-overlay\">\r\n <div className=\"lumirEditor-spinner\" />\r\n {uploadProgress !== null && (\r\n <span className=\"lumirEditor-upload-progress\">{uploadProgress}%</span>\r\n )}\r\n </div>\r\n )}\r\n\r\n {/* 에러 메시지 토스트 */}\r\n {errorMessage && (\r\n <div className=\"lumirEditor-error-toast\">\r\n <span className=\"lumirEditor-error-icon\">⚠️</span>\r\n <span className=\"lumirEditor-error-message\">{errorMessage}</span>\r\n <button\r\n className=\"lumirEditor-error-close\"\r\n onClick={() => setErrorMessage(null)}\r\n type=\"button\"\r\n >\r\n ✕\r\n </button>\r\n </div>\r\n )}\r\n </div>\r\n );\r\n}\r\n","// clsx와 tailwind-merge를 사용한 className 유틸리티\r\n// 사용자가 직접 설치하도록 권장하거나, 간단한 버전 제공\r\n\r\nexport function cn(...inputs: (string | undefined | null | false)[]) {\r\n return inputs.filter(Boolean).join(' ');\r\n}\r\n","export interface S3UploaderConfig {\r\n apiEndpoint: string; // '/api/s3/presigned'(필수)\r\n env: \"production\" | \"development\"; // 환경 (필수)\r\n path: string; // 파일 경로 (필수)\r\n /** 파일명 변환 콜백 - 확장자를 제외한 파일명을 받아 변환합니다 */\r\n fileNameTransform?: (nameWithoutExt: string, file: File) => string;\r\n /** true일 경우 파일명 뒤에 UUID를 자동으로 추가합니다 (예: image_abc123.png) */\r\n appendUUID?: boolean;\r\n /** false로 설정하면 확장자를 자동으로 붙이지 않음 (기본: true) */\r\n preserveExtension?: boolean;\r\n /** 업로드 진행률(0–100) 콜백. S3 PUT 시에만 호출됨 */\r\n onProgress?: (percent: number) => void;\r\n /** PUT 요청 타임아웃(ms). 미설정 시 120000(120초). 대용량 비디오에 유리 */\r\n uploadTimeoutMs?: number;\r\n /** PUT 실패 시 재시도 횟수. 기본 2(최대 3회 시도) */\r\n maxRetries?: number;\r\n}\r\n\r\n/**\r\n * 🔒 보안: S3 URL 검증\r\n * HTTPS 프로토콜 강제 및 URL 형식 검증\r\n */\r\nfunction validateS3Url(url: unknown, fieldName: string): string {\r\n // 타입 검증\r\n if (typeof url !== \"string\" || !url || url.trim() === \"\") {\r\n throw new Error(\r\n `${fieldName} is required and must be a non-empty string`\r\n );\r\n }\r\n\r\n // HTTPS 프로토콜 강제 (SSRF 방지)\r\n if (!url.startsWith(\"https://\")) {\r\n throw new Error(`${fieldName} must use HTTPS protocol`);\r\n }\r\n\r\n // URL 형식 검증\r\n try {\r\n const urlObj = new URL(url);\r\n // 추가 검증: localhost, private IP 차단\r\n const hostname = urlObj.hostname.toLowerCase();\r\n if (\r\n hostname === \"localhost\" ||\r\n hostname.startsWith(\"127.\") ||\r\n hostname.startsWith(\"192.168.\") ||\r\n hostname.startsWith(\"10.\") ||\r\n hostname === \"169.254.169.254\" // AWS 메타데이터 서버\r\n ) {\r\n throw new Error(`${fieldName} cannot point to internal/private networks`);\r\n }\r\n } catch (error) {\r\n if (error instanceof Error && error.message.includes(\"cannot point to\")) {\r\n throw error;\r\n }\r\n throw new Error(`${fieldName} is not a valid URL format`);\r\n }\r\n\r\n return url;\r\n}\r\n\r\n// UUID 생성 함수 (crypto.randomUUID 또는 폴백)\r\nconst generateUUID = (): string => {\r\n if (typeof crypto !== \"undefined\" && crypto.randomUUID) {\r\n return crypto.randomUUID();\r\n }\r\n // 폴백: 간단한 UUID v4 형식 생성\r\n return \"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx\".replace(/[xy]/g, (c) => {\r\n const r = (Math.random() * 16) | 0;\r\n const v = c === \"x\" ? r : (r & 0x3) | 0x8;\r\n return v.toString(16);\r\n });\r\n};\r\n\r\nexport const createS3Uploader = (config: S3UploaderConfig) => {\r\n const {\r\n apiEndpoint,\r\n env,\r\n path,\r\n fileNameTransform,\r\n appendUUID,\r\n preserveExtension = true,\r\n onProgress,\r\n uploadTimeoutMs = 120000,\r\n maxRetries = 2,\r\n } = config;\r\n\r\n // 필수 파라미터 검증\r\n if (!apiEndpoint || apiEndpoint.trim() === \"\") {\r\n throw new Error(\r\n \"apiEndpoint is required for S3 upload. Please provide a valid API endpoint.\"\r\n );\r\n }\r\n\r\n if (!env) {\r\n throw new Error(\"env is required. Must be 'development' or 'production'.\");\r\n }\r\n\r\n if (!path || path.trim() === \"\") {\r\n throw new Error(\"path is required and cannot be empty.\");\r\n }\r\n\r\n // 파일명에 UUID 추가하는 함수\r\n const appendUUIDToFileName = (filename: string): string => {\r\n const lastDotIndex = filename.lastIndexOf(\".\");\r\n if (lastDotIndex === -1) {\r\n // 확장자가 없는 경우\r\n return `${filename}_${generateUUID()}`;\r\n }\r\n const name = filename.substring(0, lastDotIndex);\r\n const ext = filename.substring(lastDotIndex);\r\n return `${name}_${generateUUID()}${ext}`;\r\n };\r\n\r\n // 계층 구조 파일명 생성 함수\r\n const generateHierarchicalFileName = (file: File): string => {\r\n // 0. 확장자 분리\r\n const originalName = file.name;\r\n const lastDotIndex = originalName.lastIndexOf(\".\");\r\n const nameWithoutExt =\r\n lastDotIndex === -1\r\n ? originalName\r\n : originalName.substring(0, lastDotIndex);\r\n const extension =\r\n lastDotIndex === -1 ? \"\" : originalName.substring(lastDotIndex);\r\n\r\n let filename = nameWithoutExt;\r\n\r\n // 1. 사용자 정의 파일명 변환 콜백 적용 (확장자 제외한 이름만)\r\n if (fileNameTransform) {\r\n filename = fileNameTransform(filename, file);\r\n }\r\n\r\n // 2. UUID 자동 추가 (appendUUID가 true인 경우)\r\n if (appendUUID) {\r\n filename = `${filename}_${generateUUID()}`;\r\n }\r\n\r\n // 3. 확장자 다시 붙이기 (preserveExtension이 true인 경우만)\r\n if (preserveExtension) {\r\n filename = `${filename}${extension}`;\r\n }\r\n\r\n // {env}/{path}/{filename}\r\n return `${env}/${path}/${filename}`;\r\n };\r\n\r\n const debugLog = (loc: string, msg: string, data: Record<string, unknown>) => {\r\n const p = fetch(\"http://127.0.0.1:7686/ingest/1f8ee1c5-0cf0-4ae7-91ed-5ea7ed17130a\", {\r\n method: \"POST\",\r\n headers: { \"Content-Type\": \"application/json\", \"X-Debug-Session-Id\": \"b73262\" },\r\n body: JSON.stringify({ sessionId: \"b73262\", location: loc, message: msg, data, timestamp: Date.now() }),\r\n });\r\n if (p && typeof (p as Promise<unknown>).catch === \"function\") (p as Promise<unknown>).catch(() => {});\r\n };\r\n\r\n return async (file: File): Promise<string> => {\r\n try {\r\n if (!apiEndpoint || apiEndpoint.trim() === \"\") {\r\n throw new Error(\r\n \"Invalid apiEndpoint: Cannot upload file without a valid API ENDPOINT\"\r\n );\r\n }\r\n\r\n const fileName = generateHierarchicalFileName(file);\r\n const contentType = file.type || \"application/octet-stream\";\r\n const presignedUrlFull = `${apiEndpoint}?key=${encodeURIComponent(fileName)}&contentType=${encodeURIComponent(contentType)}`;\r\n const tPresigned = Date.now();\r\n // #region agent log\r\n debugLog(\"s3:step1:presignedReq\", \"fetching presigned URL\", {\r\n fileName,\r\n contentType,\r\n apiEndpoint,\r\n });\r\n // #endregion\r\n const response = await fetch(presignedUrlFull);\r\n\r\n // #region agent log\r\n debugLog(\"s3:step2:presignedRes\", \"presigned response\", {\r\n ok: response.ok,\r\n status: response.status,\r\n elapsedMs: Date.now() - tPresigned,\r\n });\r\n // #endregion\r\n if (!response.ok) {\r\n const errorText = (await response.text()) || \"\";\r\n debugLog(\"s3:step2b:presignedErr\", \"presigned failed\", {\r\n status: response.status,\r\n errorText: errorText.slice(0, 200),\r\n });\r\n throw new Error(\r\n `Failed to get presigned URL: ${response.statusText}, ${errorText}`\r\n );\r\n }\r\n\r\n const responseData = await response.json();\r\n const { presignedUrl, publicUrl } = responseData;\r\n const validatedPresignedUrl = validateS3Url(presignedUrl, \"presignedUrl\");\r\n const validatedPublicUrl = validateS3Url(publicUrl, \"publicUrl\");\r\n\r\n const tPut = Date.now();\r\n // #region agent log\r\n debugLog(\"s3:step3:putReq\", \"S3 PUT request\", { publicUrlLen: validatedPublicUrl?.length });\r\n // #endregion\r\n\r\n let lastError: Error | undefined;\r\n const attempts = maxRetries + 1;\r\n for (let attempt = 0; attempt < attempts; attempt++) {\r\n try {\r\n if (onProgress && typeof XMLHttpRequest !== \"undefined\") {\r\n await new Promise<void>((resolve, reject) => {\r\n const xhr = new XMLHttpRequest();\r\n xhr.timeout = uploadTimeoutMs;\r\n\r\n // --- 진행률 보간용 상태 (progress 이벤트가 드물 때 중간 % 표시) ---\r\n let lastReported = -1; // 마지막으로 콜백에 넘긴 퍼센트 (중복 호출 방지)\r\n const REPORT_INTERVAL_MS = 100; // 보간 타이머 주기(ms)\r\n const simulatedCap = 90; // 보간 진행률 상한(%). 90 이상은 실제 progress 값 사용\r\n let simulatedPercent = 0;\r\n\r\n // 보간 타이머: progress 이벤트가 잘 오지 않을 때 주기적으로 진행률 상승\r\n const simId = setInterval(() => {\r\n if (simulatedPercent >= simulatedCap) return;\r\n simulatedPercent = Math.min(simulatedCap, simulatedPercent + 3);\r\n const p = Math.min(100, simulatedPercent);\r\n if (p > lastReported) {\r\n lastReported = p;\r\n onProgress(p);\r\n }\r\n }, REPORT_INTERVAL_MS);\r\n const clearSim = () => {\r\n clearInterval(simId);\r\n };\r\n\r\n // XHR 실제 업로드 진행률 (loaded/total)\r\n xhr.upload.onprogress = (e) => {\r\n if (e.lengthComputable) {\r\n const p = Math.min(100, Math.round((e.loaded / e.total) * 100));\r\n if (p >= simulatedCap) clearSim(); // 실제 진행이 90% 넘으면 보간 타이머 중단\r\n const toReport = Math.max(p, simulatedPercent); // 실제 vs 보간 중 큰 값 사용\r\n if (toReport > lastReported) {\r\n lastReported = toReport;\r\n onProgress(toReport);\r\n }\r\n }\r\n };\r\n xhr.onload = () => {\r\n clearSim();\r\n if (xhr.status >= 200 && xhr.status < 300) {\r\n if (lastReported < 100) onProgress(100); // 완료 시 100% 한 번 더 보장\r\n resolve();\r\n } else {\r\n reject(new Error(`Failed to upload file: ${xhr.statusText}`));\r\n }\r\n };\r\n xhr.onerror = () => {\r\n clearSim();\r\n reject(new Error(\"Upload failed\"));\r\n };\r\n xhr.ontimeout = () => {\r\n clearSim();\r\n reject(new Error(\"Upload timeout\"));\r\n };\r\n xhr.open(\"PUT\", validatedPresignedUrl);\r\n xhr.setRequestHeader(\"Content-Type\", file.type || \"application/octet-stream\");\r\n onProgress(0); // 업로드 시작 직후 0% 알림\r\n lastReported = 0;\r\n xhr.send(file);\r\n });\r\n } else {\r\n const controller = new AbortController();\r\n const timeoutId = setTimeout(() => controller.abort(), uploadTimeoutMs);\r\n const uploadResponse = await fetch(validatedPresignedUrl, {\r\n method: \"PUT\",\r\n headers: {\r\n \"Content-Type\": file.type || \"application/octet-stream\",\r\n },\r\n body: file,\r\n signal: controller.signal,\r\n });\r\n clearTimeout(timeoutId);\r\n\r\n // #region agent log\r\n debugLog(\"s3:step4:putRes\", \"S3 PUT response\", {\r\n ok: uploadResponse.ok,\r\n status: uploadResponse.status,\r\n putElapsedMs: Date.now() - tPut,\r\n });\r\n // #endregion\r\n if (!uploadResponse.ok) {\r\n throw new Error(`Failed to upload file: ${uploadResponse.statusText}`);\r\n }\r\n }\r\n debugLog(\"s3:step5:return\", \"returning publicUrl\", { urlPrefix: validatedPublicUrl.slice(0, 80) });\r\n return validatedPublicUrl;\r\n } catch (err) {\r\n lastError = err instanceof Error ? err : new Error(String(err));\r\n if (attempt < attempts - 1) {\r\n debugLog(\"s3:putRetry\", \"PUT failed, retrying\", { attempt: attempt + 1, attempts });\r\n } else {\r\n throw lastError;\r\n }\r\n }\r\n }\r\n\r\n throw lastError ?? new Error(\"Upload failed\");\r\n } catch (error) {\r\n console.error(\"S3 upload failed:\", error);\r\n throw error;\r\n }\r\n };\r\n};\r\n","import { createReactBlockSpec } from \"@blocknote/react\";\r\nimport {\r\n defaultBlockSpecs,\r\n BlockNoteSchema,\r\n defaultInlineContentSpecs,\r\n defaultStyleSpecs,\r\n createBlockSpecFromStronglyTypedTiptapNode,\r\n} from \"@blocknote/core\";\r\nimport { LinkPreviewBlock } from \"./LinkPreview\";\r\nimport { VideoBlock } from \"./VideoBlock\";\r\nimport { ColumnList } from \"./columns/ColumnList\";\r\nimport { Column } from \"./columns/Column\";\r\nimport { FontSize } from \"../styles/FontSizeStyle\";\r\nimport { useState, useRef, useCallback, useEffect } from \"react\";\r\n\r\n// HTML 미리보기 블록 속성 타입\r\nexport interface HtmlPreviewProps {\r\n htmlContent: string;\r\n fileName?: string;\r\n height?: string;\r\n}\r\n\r\n// 최소/최대 높이 상수\r\n/** @internal 테스트용 export */\r\nexport const MIN_HEIGHT = 100;\r\n/** @internal 테스트용 export */\r\nexport const MAX_HEIGHT = 1200;\r\n\r\n// ============================================\r\n// 보안 유틸리티 함수\r\n// ============================================\r\n\r\n/**\r\n * HTML에 charset이 없으면 UTF-8 meta 태그 추가\r\n * (원본 HTML을 최소한으로만 수정하여 인코딩 깨짐 방지)\r\n * @internal 테스트용 export\r\n */\r\nexport const ensureCharset = (html: string): string => {\r\n // 이미 charset이 있으면 원본 그대로 반환\r\n const hasCharset = /<meta[^>]+charset\\s*=/i.test(html);\r\n if (hasCharset) {\r\n return html;\r\n }\r\n\r\n // <head> 태그가 있으면 그 안에 추가\r\n if (/<head[^>]*>/i.test(html)) {\r\n return html.replace(/(<head[^>]*>)/i, '$1\\n<meta charset=\"UTF-8\">');\r\n }\r\n\r\n // <html> 태그만 있으면 <head> 추가\r\n if (/<html[^>]*>/i.test(html)) {\r\n return html.replace(\r\n /(<html[^>]*>)/i,\r\n '$1\\n<head><meta charset=\"UTF-8\"></head>'\r\n );\r\n }\r\n\r\n // HTML fragment인 경우 최소한의 구조 추가\r\n return `<!DOCTYPE html>\r\n<html>\r\n<head><meta charset=\"UTF-8\"></head>\r\n<body>\r\n${html}\r\n</body>\r\n</html>`;\r\n};\r\n\r\n/**\r\n * 파일명 새니타이제이션 (경로 조작 방지)\r\n * @internal 테스트용 export\r\n */\r\nexport const sanitizeFileName = (fileName: string): string => {\r\n if (!fileName || typeof fileName !== \"string\") {\r\n return `document_${Date.now()}.html`;\r\n }\r\n\r\n return (\r\n fileName\r\n .replace(/\\0/g, \"\") // Null byte 제거\r\n .replace(/[\\/\\\\]/g, \"_\") // 경로 구분자 제거\r\n .replace(/[<>:\"|?*\\x00-\\x1f]/g, \"\") // 위험한 문자 제거\r\n .replace(/\\.{2,}/g, \".\") // 연속된 점 제거\r\n .trim()\r\n .replace(/^\\.+|\\.+$/g, \"\") || `document_${Date.now()}.html` // 앞뒤 점 제거\r\n );\r\n};\r\n\r\n/**\r\n * Blob URL 생성 (UTF-8 인코딩 명시)\r\n * @internal 테스트용 export\r\n */\r\nexport const createSecureBlobUrl = (htmlContent: string): string => {\r\n const htmlWithCharset = ensureCharset(htmlContent);\r\n\r\n // UTF-8 인코딩 명시\r\n const blob = new Blob([htmlWithCharset], {\r\n type: \"text/html;charset=utf-8\",\r\n });\r\n\r\n return URL.createObjectURL(blob);\r\n};\r\n\r\n// ============================================\r\n// HTML 미리보기 블록 스펙\r\n// ============================================\r\n\r\nexport const HtmlPreviewBlock = createReactBlockSpec(\r\n {\r\n type: \"htmlPreview\",\r\n propSchema: {\r\n htmlContent: {\r\n default: \"\",\r\n },\r\n fileName: {\r\n default: \"\",\r\n },\r\n height: {\r\n default: \"400px\",\r\n },\r\n },\r\n content: \"none\",\r\n },\r\n {\r\n render: (props) => {\r\n const [isExpanded, setIsExpanded] = useState(true);\r\n const [isResizing, setIsResizing] = useState(false);\r\n const [blobUrl, setBlobUrl] = useState<string>(\"\");\r\n const containerRef = useRef<HTMLDivElement>(null);\r\n\r\n const htmlContent = props.block.props.htmlContent || \"\";\r\n const fileName = props.block.props.fileName || \"HTML Document\";\r\n const savedHeight = props.block.props.height || \"400px\";\r\n\r\n // 현재 높이 (숫자로 파싱)\r\n const currentHeight = parseInt(savedHeight, 10) || 400;\r\n\r\n // UTF-8 인코딩 보장된 Blob URL 생성\r\n useEffect(() => {\r\n if (htmlContent) {\r\n const url = createSecureBlobUrl(htmlContent);\r\n setBlobUrl(url);\r\n\r\n return () => {\r\n URL.revokeObjectURL(url);\r\n };\r\n }\r\n }, [htmlContent]);\r\n\r\n // 리사이즈 시작\r\n const handleResizeStart = useCallback(\r\n (e: React.MouseEvent) => {\r\n e.preventDefault();\r\n e.stopPropagation();\r\n setIsResizing(true);\r\n\r\n const startY = e.clientY;\r\n const startHeight = currentHeight;\r\n\r\n const handleMouseMove = (moveEvent: MouseEvent) => {\r\n const deltaY = moveEvent.clientY - startY;\r\n const newHeight = Math.min(\r\n MAX_HEIGHT,\r\n Math.max(MIN_HEIGHT, startHeight + deltaY)\r\n );\r\n\r\n // 블록 props 업데이트 (저장됨)\r\n props.editor.updateBlock(props.block, {\r\n props: { height: `${newHeight}px` },\r\n });\r\n };\r\n\r\n const handleMouseUp = () => {\r\n setIsResizing(false);\r\n document.removeEventListener(\"mousemove\", handleMouseMove);\r\n document.removeEventListener(\"mouseup\", handleMouseUp);\r\n };\r\n\r\n document.addEventListener(\"mousemove\", handleMouseMove);\r\n document.addEventListener(\"mouseup\", handleMouseUp);\r\n },\r\n [currentHeight, props.editor, props.block]\r\n );\r\n\r\n // HTML 파일 다운로드 (원본 그대로 + 인코딩 보장)\r\n const handleExport = useCallback(\r\n (e: React.MouseEvent) => {\r\n e.stopPropagation();\r\n\r\n // 파일명 새니타이제이션 (경로 조작 방지)\r\n const safeFileName = sanitizeFileName(fileName);\r\n const downloadName = safeFileName.endsWith(\".html\")\r\n ? safeFileName\r\n : `${safeFileName}.html`;\r\n\r\n // UTF-8 인코딩 명시\r\n const htmlWithCharset = ensureCharset(htmlContent);\r\n const blob = new Blob([htmlWithCharset], {\r\n type: \"text/html;charset=utf-8\",\r\n });\r\n\r\n const url = URL.createObjectURL(blob);\r\n const a = document.createElement(\"a\");\r\n a.href = url;\r\n a.download = downloadName;\r\n a.rel = \"noopener noreferrer\"; // 보안 속성 추가\r\n\r\n document.body.appendChild(a);\r\n a.click();\r\n document.body.removeChild(a);\r\n URL.revokeObjectURL(url);\r\n },\r\n [htmlContent, fileName]\r\n );\r\n\r\n // 새 창에서 열기 (Blob URL 방식 - XSS 방지)\r\n const handleOpenNewWindow = useCallback(\r\n (e: React.MouseEvent) => {\r\n e.stopPropagation();\r\n\r\n // 클라이언트 사이드에서만 실행\r\n if (typeof window === \"undefined\") return;\r\n\r\n // Blob URL 생성 (UTF-8 인코딩 보장)\r\n const url = createSecureBlobUrl(htmlContent);\r\n\r\n // noopener, noreferrer로 보안 강화\r\n const newWindow = window.open(url, \"_blank\", \"noopener,noreferrer\");\r\n\r\n // Blob URL 정리\r\n if (newWindow) {\r\n setTimeout(() => URL.revokeObjectURL(url), 1000);\r\n } else {\r\n URL.revokeObjectURL(url);\r\n }\r\n },\r\n [htmlContent]\r\n );\r\n\r\n return (\r\n <div\r\n ref={containerRef}\r\n style={{\r\n border: \"1px solid #e0e0e0\",\r\n borderRadius: \"8px\",\r\n overflow: \"hidden\",\r\n backgroundColor: \"#f9f9f9\",\r\n marginBottom: \"2px\",\r\n width: \"100%\",\r\n userSelect: isResizing ? \"none\" : \"auto\",\r\n outline: \"none\",\r\n boxShadow: \"none\",\r\n }}\r\n >\r\n {/* 헤더 */}\r\n <div\r\n style={{\r\n display: \"flex\",\r\n alignItems: \"center\",\r\n justifyContent: \"space-between\",\r\n padding: \"4px 16px\",\r\n backgroundColor: \"#fff\",\r\n borderBottom: isExpanded ? \"1px solid #e0e0e0\" : \"none\",\r\n }}\r\n >\r\n <div\r\n style={{\r\n display: \"flex\",\r\n alignItems: \"center\",\r\n gap: \"8px\",\r\n cursor: \"pointer\",\r\n flex: 1,\r\n }}\r\n onClick={() => setIsExpanded(!isExpanded)}\r\n >\r\n <svg\r\n width=\"16\"\r\n height=\"16\"\r\n viewBox=\"0 0 24 24\"\r\n fill=\"none\"\r\n stroke=\"currentColor\"\r\n strokeWidth=\"2\"\r\n strokeLinecap=\"round\"\r\n strokeLinejoin=\"round\"\r\n style={{\r\n transform: isExpanded ? \"rotate(180deg)\" : \"rotate(0deg)\",\r\n transition: \"transform 0.2s\",\r\n }}\r\n >\r\n <polyline points=\"6 9 12 15 18 9\"></polyline>\r\n </svg>\r\n\r\n <span style={{ fontWeight: 500, fontSize: \"14px\" }}>\r\n {fileName}\r\n </span>\r\n </div>\r\n\r\n {/* 액션 버튼들 */}\r\n <div style={{ display: \"flex\", alignItems: \"center\", gap: \"4px\" }}>\r\n {/* 새 창에서 열기 버튼 */}\r\n <button\r\n onClick={handleOpenNewWindow}\r\n style={{\r\n background: \"none\",\r\n border: \"none\",\r\n cursor: \"pointer\",\r\n padding: \"4px\",\r\n display: \"flex\",\r\n alignItems: \"center\",\r\n justifyContent: \"center\",\r\n color: \"#666\",\r\n borderRadius: \"4px\",\r\n }}\r\n title=\"새 창에서 열기\"\r\n type=\"button\"\r\n onMouseEnter={(e) => {\r\n (e.currentTarget as HTMLButtonElement).style.backgroundColor =\r\n \"#f0f0f0\";\r\n }}\r\n onMouseLeave={(e) => {\r\n (e.currentTarget as HTMLButtonElement).style.backgroundColor =\r\n \"transparent\";\r\n }}\r\n >\r\n <svg\r\n width=\"16\"\r\n height=\"16\"\r\n viewBox=\"0 0 24 24\"\r\n fill=\"none\"\r\n stroke=\"currentColor\"\r\n strokeWidth=\"2\"\r\n strokeLinecap=\"round\"\r\n strokeLinejoin=\"round\"\r\n >\r\n <path d=\"M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6\"></path>\r\n <polyline points=\"15 3 21 3 21 9\"></polyline>\r\n <line x1=\"10\" y1=\"14\" x2=\"21\" y2=\"3\"></line>\r\n </svg>\r\n </button>\r\n\r\n {/* 다운로드 버튼 */}\r\n <button\r\n onClick={handleExport}\r\n style={{\r\n background: \"none\",\r\n border: \"none\",\r\n cursor: \"pointer\",\r\n padding: \"4px\",\r\n display: \"flex\",\r\n alignItems: \"center\",\r\n justifyContent: \"center\",\r\n color: \"#666\",\r\n borderRadius: \"4px\",\r\n }}\r\n title=\"HTML 다운로드\"\r\n type=\"button\"\r\n onMouseEnter={(e) => {\r\n (e.currentTarget as HTMLButtonElement).style.backgroundColor =\r\n \"#f0f0f0\";\r\n }}\r\n onMouseLeave={(e) => {\r\n (e.currentTarget as HTMLButtonElement).style.backgroundColor =\r\n \"transparent\";\r\n }}\r\n >\r\n <svg\r\n width=\"16\"\r\n height=\"16\"\r\n viewBox=\"0 0 24 24\"\r\n fill=\"none\"\r\n stroke=\"currentColor\"\r\n strokeWidth=\"2\"\r\n strokeLinecap=\"round\"\r\n strokeLinejoin=\"round\"\r\n >\r\n <path d=\"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4\"></path>\r\n <polyline points=\"7 10 12 15 17 10\"></polyline>\r\n <line x1=\"12\" y1=\"15\" x2=\"12\" y2=\"3\"></line>\r\n </svg>\r\n </button>\r\n </div>\r\n </div>\r\n\r\n {/* iframe 미리보기 */}\r\n {isExpanded && (\r\n <div\r\n style={{\r\n padding: \"0\",\r\n backgroundColor: \"#fff\",\r\n position: \"relative\",\r\n }}\r\n >\r\n {/* 🔒 보안 강화: JavaScript 완전 차단 + 부모 페이지 접근 차단 */}\r\n <iframe\r\n src={blobUrl || \"about:blank\"}\r\n style={{\r\n width: \"100%\",\r\n height: `${currentHeight}px`,\r\n border: \"none\",\r\n display: \"block\",\r\n pointerEvents: isResizing ? \"none\" : \"auto\",\r\n }}\r\n // 🔒 allow-scripts 제거 = JavaScript 실행 차단\r\n // 🔒 allow-same-origin 제거 = 부모 페이지 접근 차단\r\n // ✅ HTML + CSS만 렌더링 (안전)\r\n sandbox=\"allow-popups allow-forms\"\r\n title={fileName}\r\n referrerPolicy=\"no-referrer\"\r\n loading=\"lazy\"\r\n />\r\n\r\n {/* 리사이즈 핸들 */}\r\n <div\r\n onMouseDown={handleResizeStart}\r\n style={{\r\n position: \"absolute\",\r\n bottom: 0,\r\n left: 0,\r\n right: 0,\r\n height: \"12px\",\r\n cursor: \"ns-resize\",\r\n backgroundColor: isResizing\r\n ? \"rgba(59, 130, 246, 0.3)\"\r\n : \"transparent\",\r\n display: \"flex\",\r\n alignItems: \"center\",\r\n justifyContent: \"center\",\r\n transition: \"background-color 0.2s\",\r\n }}\r\n onMouseEnter={(e) => {\r\n (e.currentTarget as HTMLDivElement).style.backgroundColor =\r\n \"rgba(59, 130, 246, 0.2)\";\r\n }}\r\n onMouseLeave={(e) => {\r\n if (!isResizing) {\r\n (e.currentTarget as HTMLDivElement).style.backgroundColor =\r\n \"transparent\";\r\n }\r\n }}\r\n >\r\n {/* 리사이즈 핸들 아이콘 */}\r\n <div\r\n style={{\r\n width: \"40px\",\r\n height: \"4px\",\r\n backgroundColor: \"#ccc\",\r\n borderRadius: \"2px\",\r\n }}\r\n />\r\n </div>\r\n </div>\r\n )}\r\n </div>\r\n );\r\n },\r\n }\r\n);\r\n\r\n// 다단(컬럼) 블록 스펙 — column/columnList Tiptap 노드를 BlockNote 블록 스펙으로 등록.\r\n// content는 자동으로 \"none\"으로 매핑되고 children은 childContainer에서 읽힌다 → nodeToBlock\r\n// (직렬화)이 columnList/column을 인식해 라운드트립된다.\r\nconst ColumnListBlock = createBlockSpecFromStronglyTypedTiptapNode(\r\n ColumnList,\r\n // showDivider를 블록 prop으로 등록 → onContentChange JSON 직렬화 + 재로드 라운드트립.\r\n { showDivider: { default: false } },\r\n);\r\nconst ColumnBlock = createBlockSpecFromStronglyTypedTiptapNode(Column, {});\r\n\r\n// 커스텀 블록이 포함된 스키마 생성\r\nexport const schema = BlockNoteSchema.create({\r\n blockSpecs: {\r\n ...defaultBlockSpecs,\r\n htmlPreview: HtmlPreviewBlock,\r\n linkPreview: LinkPreviewBlock,\r\n video: VideoBlock,\r\n columnList: ColumnListBlock,\r\n column: ColumnBlock,\r\n },\r\n inlineContentSpecs: defaultInlineContentSpecs,\r\n styleSpecs: {\r\n ...defaultStyleSpecs,\r\n // 인라인 글자 크기. 저장 JSON 직렬화는 형제 키 방식(font-size-serialization.ts) 사용.\r\n fontSize: FontSize,\r\n },\r\n});\r\n\r\n// 스키마 타입 export\r\nexport type HtmlPreviewSchema = typeof schema;\r\n","import { createReactBlockSpec } from \"@blocknote/react\";\r\nimport React, { useState, useEffect, useCallback, useRef } from \"react\";\r\nimport { DEFAULT_LINK_PREVIEW_IMAGE } from \"./defaultLogo\";\r\n\r\nexport interface LinkMetadata {\r\n url: string;\r\n title: string;\r\n description?: string;\r\n image?: string;\r\n domain: string;\r\n}\r\n\r\nconst metadataCache = new Map<string, LinkMetadata>();\r\n\r\nexport async function fetchLinkMetadata(\r\n url: string,\r\n apiEndpoint: string\r\n): Promise<LinkMetadata> {\r\n const cached = metadataCache.get(url);\r\n if (cached) return cached;\r\n\r\n const response = await fetch(\r\n `${apiEndpoint}?url=${encodeURIComponent(url)}`\r\n );\r\n if (!response.ok) {\r\n throw new Error(`Failed to fetch metadata: ${response.status}`);\r\n }\r\n\r\n const metadata: LinkMetadata = await response.json();\r\n metadataCache.set(url, metadata);\r\n return metadata;\r\n}\r\n\r\nexport function clearMetadataCache() {\r\n metadataCache.clear();\r\n}\r\n\r\nfunction extractDomain(url: string): string {\r\n try {\r\n return new URL(url).hostname.replace(/^www\\./, \"\");\r\n } catch {\r\n return url;\r\n }\r\n}\r\n\r\nconst LinkPreviewCard = ({\r\n url,\r\n title,\r\n description,\r\n image,\r\n domain,\r\n editable,\r\n onDelete,\r\n width,\r\n onWidthChange,\r\n height,\r\n onHeightChange,\r\n}: {\r\n url: string;\r\n title: string;\r\n description: string;\r\n image: string;\r\n domain: string;\r\n editable: boolean;\r\n onDelete?: () => void;\r\n width?: number;\r\n onWidthChange?: (width: number) => void;\r\n height?: number;\r\n onHeightChange?: (height: number) => void;\r\n}) => {\r\n const [imgError, setImgError] = useState(false);\r\n const [hovered, setHovered] = useState(false);\r\n\r\n type ResizeParams = {\r\n handleUsed: \"left\" | \"right\" | \"bottom\";\r\n initialClientX: number;\r\n initialClientY: number;\r\n initialWidth: number;\r\n initialHeight: number;\r\n };\r\n const [resizeParams, setResizeParams] = useState<ResizeParams | undefined>(undefined);\r\n const [localWidth, setLocalWidth] = useState<number | undefined>(width);\r\n const [localHeight, setLocalHeight] = useState<number | undefined>(height);\r\n const cardRef = useRef<HTMLDivElement>(null);\r\n const justResizedRef = useRef(false);\r\n\r\n useEffect(() => { setLocalWidth(width); }, [width]);\r\n useEffect(() => { setLocalHeight(height); }, [height]);\r\n\r\n useEffect(() => {\r\n if (!resizeParams) return;\r\n\r\n const onMouseMove = (e: MouseEvent) => {\r\n if (resizeParams.handleUsed === \"bottom\") {\r\n const delta = e.clientY - resizeParams.initialClientY;\r\n setLocalHeight(Math.min(Math.max(resizeParams.initialHeight + delta, 100), 600));\r\n } else {\r\n const delta = resizeParams.handleUsed === \"left\"\r\n ? resizeParams.initialClientX - e.clientX\r\n : e.clientX - resizeParams.initialClientX;\r\n setLocalWidth(Math.min(\r\n Math.max(resizeParams.initialWidth + delta, 200),\r\n cardRef.current?.parentElement?.clientWidth || 800\r\n ));\r\n }\r\n };\r\n\r\n const onMouseUp = () => {\r\n const handle = resizeParams.handleUsed;\r\n setResizeParams(undefined);\r\n justResizedRef.current = true;\r\n setTimeout(() => { justResizedRef.current = false; }, 50);\r\n if (handle === \"bottom\") {\r\n if (localHeight && onHeightChange) onHeightChange(localHeight);\r\n } else {\r\n if (localWidth && onWidthChange) onWidthChange(localWidth);\r\n }\r\n };\r\n\r\n window.addEventListener(\"mousemove\", onMouseMove);\r\n window.addEventListener(\"mouseup\", onMouseUp);\r\n return () => {\r\n window.removeEventListener(\"mousemove\", onMouseMove);\r\n window.removeEventListener(\"mouseup\", onMouseUp);\r\n };\r\n }, [resizeParams, localWidth, localHeight, onWidthChange, onHeightChange]);\r\n\r\n const handleLeftDown = useCallback((e: React.MouseEvent) => {\r\n e.preventDefault();\r\n e.stopPropagation();\r\n setResizeParams({\r\n handleUsed: \"left\",\r\n initialWidth: cardRef.current!.clientWidth,\r\n initialHeight: localHeight || 200,\r\n initialClientX: e.clientX,\r\n initialClientY: e.clientY,\r\n });\r\n }, [localHeight]);\r\n\r\n const handleRightDown = useCallback((e: React.MouseEvent) => {\r\n e.preventDefault();\r\n e.stopPropagation();\r\n setResizeParams({\r\n handleUsed: \"right\",\r\n initialWidth: cardRef.current!.clientWidth,\r\n initialHeight: localHeight || 200,\r\n initialClientX: e.clientX,\r\n initialClientY: e.clientY,\r\n });\r\n }, [localHeight]);\r\n\r\n const handleBottomDown = useCallback((e: React.MouseEvent) => {\r\n e.preventDefault();\r\n e.stopPropagation();\r\n setResizeParams({\r\n handleUsed: \"bottom\",\r\n initialWidth: cardRef.current!.clientWidth,\r\n initialHeight: localHeight || 200,\r\n initialClientX: e.clientX,\r\n initialClientY: e.clientY,\r\n });\r\n }, [localHeight]);\r\n\r\n const handleClick = useCallback(() => {\r\n if (url && !resizeParams && !justResizedRef.current) {\r\n window.open(url, \"_blank\", \"noopener,noreferrer\");\r\n }\r\n }, [url, resizeParams]);\r\n\r\n const resizeCursor = resizeParams\r\n ? resizeParams.handleUsed === \"bottom\" ? \"ns-resize\" : \"ew-resize\"\r\n : \"pointer\";\r\n\r\n return (\r\n <div\r\n ref={cardRef}\r\n className=\"lumir-link-preview-card\"\r\n onClick={handleClick}\r\n style={{\r\n border: \"1px solid #e0e0e0\",\r\n borderRadius: \"8px\",\r\n overflow: \"hidden\",\r\n cursor: resizeCursor,\r\n width: localWidth ? `${localWidth}px` : undefined,\r\n maxWidth: \"100%\",\r\n backgroundColor: \"#fff\",\r\n transition: resizeParams ? \"none\" : \"box-shadow 0.2s\",\r\n position: \"relative\",\r\n }}\r\n onMouseEnter={() => {\r\n if (!resizeParams) setHovered(true);\r\n }}\r\n onMouseLeave={() => {\r\n if (!resizeParams) setHovered(false);\r\n }}\r\n >\r\n {editable && onDelete && (\r\n <button\r\n type=\"button\"\r\n onClick={(e) => {\r\n e.stopPropagation();\r\n onDelete();\r\n }}\r\n className=\"lumir-link-preview-delete\"\r\n style={{\r\n position: \"absolute\",\r\n top: \"8px\",\r\n right: \"8px\",\r\n width: \"24px\",\r\n height: \"24px\",\r\n borderRadius: \"50%\",\r\n border: \"none\",\r\n backgroundColor: \"rgba(0,0,0,0.5)\",\r\n color: \"#fff\",\r\n cursor: \"pointer\",\r\n display: \"flex\",\r\n alignItems: \"center\",\r\n justifyContent: \"center\",\r\n fontSize: \"14px\",\r\n lineHeight: 1,\r\n zIndex: 2,\r\n opacity: 0,\r\n transition: \"opacity 0.2s\",\r\n }}\r\n title=\"삭제\"\r\n >\r\n ✕\r\n </button>\r\n )}\r\n\r\n {editable && (hovered || resizeParams) && (\r\n <>\r\n <div\r\n className=\"lumir-resize-handle\"\r\n style={{ left: \"4px\" }}\r\n onMouseDown={handleLeftDown}\r\n />\r\n <div\r\n className=\"lumir-resize-handle\"\r\n style={{ right: \"4px\" }}\r\n onMouseDown={handleRightDown}\r\n />\r\n </>\r\n )}\r\n\r\n {image && !imgError && (\r\n <div\r\n style={{\r\n width: \"100%\",\r\n height: `${localHeight || 200}px`,\r\n overflow: \"hidden\",\r\n backgroundColor: \"#f0f0f0\",\r\n position: \"relative\",\r\n }}\r\n >\r\n <img\r\n src={image}\r\n alt={title || \"Link preview\"}\r\n onError={() => setImgError(true)}\r\n style={{\r\n width: \"100%\",\r\n height: \"100%\",\r\n objectFit: \"cover\",\r\n display: \"block\",\r\n }}\r\n referrerPolicy=\"no-referrer\"\r\n loading=\"lazy\"\r\n />\r\n {editable && (hovered || resizeParams) && (\r\n <div\r\n className=\"lumir-resize-handle-bottom\"\r\n onMouseDown={handleBottomDown}\r\n />\r\n )}\r\n </div>\r\n )}\r\n\r\n <div style={{ padding: \"12px 16px\" }}>\r\n <div\r\n style={{\r\n fontSize: \"14px\",\r\n fontWeight: 600,\r\n color: \"#1a1a1a\",\r\n lineHeight: 1.4,\r\n marginBottom: description ? \"4px\" : \"8px\",\r\n overflow: \"hidden\",\r\n textOverflow: \"ellipsis\",\r\n whiteSpace: \"nowrap\",\r\n }}\r\n >\r\n {title || domain}\r\n </div>\r\n\r\n {description && (\r\n <div\r\n style={{\r\n fontSize: \"12px\",\r\n color: \"#666\",\r\n lineHeight: 1.4,\r\n marginBottom: \"8px\",\r\n overflow: \"hidden\",\r\n textOverflow: \"ellipsis\",\r\n display: \"-webkit-box\",\r\n WebkitLineClamp: 2,\r\n WebkitBoxOrient: \"vertical\",\r\n }}\r\n >\r\n {description}\r\n </div>\r\n )}\r\n\r\n <div\r\n style={{\r\n fontSize: \"12px\",\r\n color: \"#999\",\r\n overflow: \"hidden\",\r\n textOverflow: \"ellipsis\",\r\n whiteSpace: \"nowrap\",\r\n }}\r\n >\r\n {domain}\r\n </div>\r\n </div>\r\n </div>\r\n );\r\n};\r\n\r\nconst LinkPreviewSkeleton = () => (\r\n <div\r\n className=\"lumir-link-preview-skeleton\"\r\n style={{\r\n border: \"1px solid #e0e0e0\",\r\n borderRadius: \"8px\",\r\n overflow: \"hidden\",\r\n maxWidth: \"400px\",\r\n backgroundColor: \"#fff\",\r\n }}\r\n >\r\n <div\r\n style={{\r\n width: \"100%\",\r\n height: \"200px\",\r\n backgroundColor: \"#f0f0f0\",\r\n animation: \"lumir-skeleton-pulse 1.5s ease-in-out infinite\",\r\n }}\r\n />\r\n <div style={{ padding: \"12px 16px\" }}>\r\n <div\r\n style={{\r\n height: \"16px\",\r\n width: \"70%\",\r\n backgroundColor: \"#f0f0f0\",\r\n borderRadius: \"4px\",\r\n marginBottom: \"8px\",\r\n animation: \"lumir-skeleton-pulse 1.5s ease-in-out infinite\",\r\n }}\r\n />\r\n <div\r\n style={{\r\n height: \"12px\",\r\n width: \"90%\",\r\n backgroundColor: \"#f0f0f0\",\r\n borderRadius: \"4px\",\r\n marginBottom: \"8px\",\r\n animation: \"lumir-skeleton-pulse 1.5s ease-in-out infinite\",\r\n }}\r\n />\r\n <div\r\n style={{\r\n height: \"12px\",\r\n width: \"40%\",\r\n backgroundColor: \"#f0f0f0\",\r\n borderRadius: \"4px\",\r\n animation: \"lumir-skeleton-pulse 1.5s ease-in-out infinite\",\r\n }}\r\n />\r\n </div>\r\n </div>\r\n);\r\n\r\nconst LinkPreviewError = ({\r\n url,\r\n onRetry,\r\n}: {\r\n url: string;\r\n onRetry?: () => void;\r\n}) => {\r\n const domain = (() => {\r\n try { return new URL(url).hostname; } catch { return url; }\r\n })();\r\n\r\n return (\r\n <div\r\n className=\"lumir-link-preview-error\"\r\n style={{\r\n border: \"1px solid #e0e0e0\",\r\n borderRadius: \"8px\",\r\n overflow: \"hidden\",\r\n maxWidth: \"400px\",\r\n backgroundColor: \"#fff\",\r\n cursor: \"pointer\",\r\n position: \"relative\",\r\n }}\r\n onClick={() => window.open(url, \"_blank\", \"noopener,noreferrer\")}\r\n >\r\n <div\r\n style={{\r\n width: \"100%\",\r\n height: \"160px\",\r\n overflow: \"hidden\",\r\n backgroundColor: \"#f0f0f0\",\r\n display: \"flex\",\r\n alignItems: \"center\",\r\n justifyContent: \"center\",\r\n }}\r\n >\r\n <img\r\n src={DEFAULT_LINK_PREVIEW_IMAGE}\r\n alt=\"Lumir\"\r\n style={{\r\n width: \"100%\",\r\n height: \"100%\",\r\n objectFit: \"contain\",\r\n display: \"block\",\r\n padding: \"20px\",\r\n boxSizing: \"border-box\",\r\n }}\r\n />\r\n </div>\r\n <div style={{ padding: \"12px 16px\" }}>\r\n <div\r\n style={{\r\n fontSize: \"13px\",\r\n color: \"#999\",\r\n marginBottom: \"4px\",\r\n }}\r\n >\r\n 미리보기를 불러올 수 없습니다\r\n </div>\r\n <div\r\n style={{\r\n fontSize: \"12px\",\r\n color: \"#1a73e8\",\r\n overflow: \"hidden\",\r\n textOverflow: \"ellipsis\",\r\n whiteSpace: \"nowrap\",\r\n }}\r\n >\r\n {domain}\r\n </div>\r\n </div>\r\n {onRetry && (\r\n <button\r\n type=\"button\"\r\n onClick={(e) => { e.stopPropagation(); onRetry(); }}\r\n style={{\r\n position: \"absolute\",\r\n top: \"8px\",\r\n right: \"8px\",\r\n background: \"rgba(0,0,0,0.5)\",\r\n border: \"none\",\r\n borderRadius: \"4px\",\r\n padding: \"4px 8px\",\r\n cursor: \"pointer\",\r\n fontSize: \"12px\",\r\n color: \"#fff\",\r\n whiteSpace: \"nowrap\",\r\n }}\r\n >\r\n 재시도\r\n </button>\r\n )}\r\n </div>\r\n );\r\n};\r\n\r\nconst LinkPreviewUrlInput = ({\r\n onSubmit,\r\n}: {\r\n onSubmit: (url: string) => void;\r\n}) => {\r\n const [inputUrl, setInputUrl] = useState(\"\");\r\n const inputRef = useRef<HTMLInputElement>(null);\r\n\r\n useEffect(() => {\r\n inputRef.current?.focus();\r\n }, []);\r\n\r\n const handleSubmit = useCallback(() => {\r\n const trimmed = inputUrl.trim();\r\n if (!trimmed) return;\r\n const normalized = /^https?:\\/\\//i.test(trimmed)\r\n ? trimmed\r\n : `https://${trimmed}`;\r\n onSubmit(normalized);\r\n }, [inputUrl, onSubmit]);\r\n\r\n return (\r\n <div\r\n className=\"lumir-link-preview-input\"\r\n style={{\r\n border: \"1px solid #e0e0e0\",\r\n borderRadius: \"8px\",\r\n padding: \"16px\",\r\n maxWidth: \"400px\",\r\n backgroundColor: \"#fafafa\",\r\n display: \"flex\",\r\n alignItems: \"center\",\r\n gap: \"8px\",\r\n }}\r\n >\r\n <svg\r\n width=\"18\"\r\n height=\"18\"\r\n viewBox=\"0 0 24 24\"\r\n fill=\"none\"\r\n stroke=\"#999\"\r\n strokeWidth=\"2\"\r\n strokeLinecap=\"round\"\r\n strokeLinejoin=\"round\"\r\n style={{ flexShrink: 0 }}\r\n >\r\n <path d=\"M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71\" />\r\n <path d=\"M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71\" />\r\n </svg>\r\n <input\r\n ref={inputRef}\r\n type=\"text\"\r\n value={inputUrl}\r\n onChange={(e) => setInputUrl(e.target.value)}\r\n onKeyDown={(e) => {\r\n if (e.key === \"Enter\") {\r\n e.preventDefault();\r\n handleSubmit();\r\n }\r\n }}\r\n placeholder=\"URL을 입력하세요 (예: https://example.com)\"\r\n style={{\r\n flex: 1,\r\n border: \"none\",\r\n outline: \"none\",\r\n backgroundColor: \"transparent\",\r\n fontSize: \"13px\",\r\n color: \"#333\",\r\n minWidth: 0,\r\n }}\r\n />\r\n <button\r\n type=\"button\"\r\n onClick={handleSubmit}\r\n disabled={!inputUrl.trim()}\r\n style={{\r\n flexShrink: 0,\r\n padding: \"4px 12px\",\r\n border: \"none\",\r\n borderRadius: \"4px\",\r\n backgroundColor: inputUrl.trim() ? \"#3b82f6\" : \"#d1d5db\",\r\n color: \"#fff\",\r\n fontSize: \"12px\",\r\n fontWeight: 500,\r\n cursor: inputUrl.trim() ? \"pointer\" : \"not-allowed\",\r\n transition: \"background-color 0.15s\",\r\n }}\r\n >\r\n 확인\r\n </button>\r\n </div>\r\n );\r\n};\r\n\r\nexport const LinkPreviewBlock = createReactBlockSpec(\r\n {\r\n type: \"linkPreview\",\r\n propSchema: {\r\n url: { default: \"\" },\r\n title: { default: \"\" },\r\n description: { default: \"\" },\r\n image: { default: \"\" },\r\n domain: { default: \"\" },\r\n previewWidth: { default: 400 as const },\r\n previewHeight: { default: 200 as const },\r\n },\r\n content: \"none\",\r\n },\r\n {\r\n render: (props) => {\r\n const { url, title, description, image, domain } =\r\n props.block.props;\r\n const [status, setStatus] = useState<\"idle\" | \"loading\" | \"error\">(\r\n !url ? \"idle\" : title ? \"idle\" : \"loading\"\r\n );\r\n const fetchedRef = useRef(false);\r\n\r\n const editable = props.editor.isEditable;\r\n\r\n useEffect(() => {\r\n if (!url || title || fetchedRef.current) {\r\n if (!url) setStatus(\"idle\");\r\n return;\r\n }\r\n\r\n const getApiEndpoint = () =>\r\n (props.editor as any)._linkPreviewApiEndpoint as\r\n | string\r\n | undefined;\r\n\r\n const doFetch = (endpoint: string) => {\r\n fetchedRef.current = true;\r\n setStatus(\"loading\");\r\n\r\n fetchLinkMetadata(url, endpoint)\r\n .then((metadata) => {\r\n props.editor.updateBlock(props.block, {\r\n props: {\r\n title: metadata.title || \"\",\r\n description: metadata.description || \"\",\r\n image: metadata.image || \"\",\r\n domain: metadata.domain || extractDomain(url),\r\n },\r\n });\r\n setStatus(\"idle\");\r\n })\r\n .catch(() => {\r\n props.editor.updateBlock(props.block, {\r\n props: { domain: extractDomain(url) },\r\n });\r\n setStatus(\"error\");\r\n });\r\n };\r\n\r\n const endpoint = getApiEndpoint();\r\n if (endpoint) {\r\n doFetch(endpoint);\r\n } else {\r\n // endpoint가 아직 설정되지 않았을 수 있으므로 잠시 대기 후 재시도\r\n const timer = setTimeout(() => {\r\n const retryEndpoint = getApiEndpoint();\r\n if (retryEndpoint) {\r\n doFetch(retryEndpoint);\r\n } else {\r\n setStatus(\"error\");\r\n }\r\n }, 100);\r\n return () => clearTimeout(timer);\r\n }\r\n }, [url, title]);\r\n\r\n const handleRetry = useCallback(() => {\r\n const endpoint = (props.editor as any)._linkPreviewApiEndpoint as\r\n | string\r\n | undefined;\r\n if (!url || !endpoint) return;\r\n metadataCache.delete(url);\r\n fetchedRef.current = false;\r\n setStatus(\"loading\");\r\n\r\n fetchLinkMetadata(url, endpoint)\r\n .then((metadata) => {\r\n props.editor.updateBlock(props.block, {\r\n props: {\r\n title: metadata.title || \"\",\r\n description: metadata.description || \"\",\r\n image: metadata.image || \"\",\r\n domain: metadata.domain || extractDomain(url),\r\n },\r\n });\r\n setStatus(\"idle\");\r\n })\r\n .catch(() => {\r\n setStatus(\"error\");\r\n });\r\n }, [url, props.editor, props.block]);\r\n\r\n const handleDelete = useCallback(() => {\r\n props.editor.removeBlocks([props.block]);\r\n }, [props.editor, props.block]);\r\n\r\n if (!url) {\r\n if (!editable) {\r\n return null;\r\n }\r\n return (\r\n <LinkPreviewUrlInput\r\n onSubmit={(newUrl) => {\r\n props.editor.updateBlock(props.block, {\r\n props: { url: newUrl },\r\n });\r\n }}\r\n />\r\n );\r\n }\r\n\r\n if (status === \"loading\") {\r\n return <LinkPreviewSkeleton />;\r\n }\r\n\r\n if (status === \"error\") {\r\n return <LinkPreviewError url={url} onRetry={handleRetry} />;\r\n }\r\n\r\n return (\r\n <LinkPreviewCard\r\n url={url}\r\n title={title}\r\n description={description}\r\n image={image}\r\n domain={domain || extractDomain(url)}\r\n editable={editable}\r\n onDelete={handleDelete}\r\n width={props.block.props.previewWidth}\r\n onWidthChange={(newWidth) => {\r\n props.editor.updateBlock(props.block, {\r\n props: { previewWidth: newWidth },\r\n });\r\n }}\r\n height={props.block.props.previewHeight}\r\n onHeightChange={(newHeight) => {\r\n props.editor.updateBlock(props.block, {\r\n props: { previewHeight: newHeight },\r\n });\r\n }}\r\n />\r\n );\r\n },\r\n }\r\n);\r\n","export const DEFAULT_LINK_PREVIEW_IMAGE = \"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAABrUAAAHsCAYAAAB8ECZhAAAACXBIWXMAAAsTAAALEwEAmpwYAAE1r0lEQVR4nOzdd5gT5cLG4V+yjaUuvU8gdBBQBBEr2AB7P/buZz32Ati7HnvXY+8ioB57FwF7b0iRSELvvW2b748syy47gWU3yTuZee7r8iJ5Z+adZ3YXhHkyMwFEJLM8+EcWtt0AyAfyKiwJYtsNy9/ZAKwGu7jCWBGwBljDxX2L0hFXRERERERERERERCQZAqYDiPjSI1MaAK3Bbg60wqYF0BwoALsg/isF2Hbjstf52HYdoCG27fz71rYrvK7yotJL7NKNYyuAQmAt2CuA5cAybJaXvV4O9iJgETbzgYXAAi7fcdm2H7SIiIiIiIiIiIiISM2p1BJJtsem5QOdwbYAC9u2gPZAe2zaA22AvPKWqWLZVKmEcnhtV1o5wbpOc1Vct7TqWKJ1E2csAuZTas8CZoEdBWYDMWxiwN+M2GmVc1gRERERERERERERkW2nUkukJv47PQfoik0PoBsQxrY7A52BNpvKoG0uixy2c12hFVdqVx2rtC97MTAN+BuIANOx7cnAFEbtvL7qgYiIiIiIiIiIiIiIJKZSS2RLnpgRIH7V1fZAL2y7F9CTeKGVXb5epcJJhZZj3k1jpdh2BPgz/p89GfgFmMJVu5RU3VBERERERERERERERKWWyCZPzsgCegE7YrMDsAOwPdj1gcQllQqtbSm0nL9e8Zfrgd+w7Z+Bn4GfgF+5ZtfCqhOKiIiIiIiIiIiIiN+o1BL/evqf1tj2QGBnbHtnoD9Qz7ngUaEFpLLQSrT9BuBnbPsb4BtsvuW63WZW3YmIiIiIiIiIiIiIeJ1KLfGPp//pDAwG9gB2x7Y7ANUooVRoASYKrcrjmxbPB3sCMBEYj23/yfV7JNhYRERERERERERERLxCpZZ41zMzO2Hb+wBDiJdZLcuXORVHKrQyodDC4Wu3DJiAbY8HPuWGPX93nkxEREREREREREREMplKLfGOZ6ONse29gP2AfbHtjo7rqdDyUqHltO484GOwPwE+4sbBC5x3ICIiIiIiIiIiIiKZRKWWZLZno32BA4EDyp6PFQSqUZSo0KpWxswrtBxC8ivwDrb9DvAdNw0pdd6piIiIiIiIiIiIiLiZSi3JLM/H8ii192ZjkQUWsIUiharjKrSql9Ebhdbm2yzC5j3gbeBDbh6y2jmEiIiIiIiIiIiIiLiNSi1xv+dj9YBhwFGU2vsDDSotV6FV9lqF1lYKrc0XbwA+Bl7Dtt/hlr2WOYcSERERERERERERETdQqSXu9Pys+sBBYB8J7A/U2VS0VKBCq+y1Cq1tLLQ2X6cY2/60fm7Wmwd2b/bWq8dsNzfB2iIiIiIiIiIiIiJiiEotcY8XZudi20OB44GDwc4vX6ZCa7O5Kq6rQquWhVb5rxcMaset+3Viwszlc36dt+rFtyYvuvXrc3damWBrEREREREREREREUkjlVpi1otzAtj27sDx2PZRQOP4AqeipQIVWmWvVWglq9BqkJdF5LJdaFY3p3yVxWuK7Ikzl03/Ze6qx2/+7J/7S2/ftyTBbCIiIiIiIiIiIiKSYiq1xIwX57QDTsG2TwE6JSxEVGhtNlfFdVVoJavQArhxn45cM6Rjgg0gsmRd8cSZy778YfbKqx86tMekhCuKiIiIiIiIiIiISEqo1JL0eWluHrZ9GHAqsC+2Hf/5U6G1hX2o0KrWuo7bOO/W6fvQukEu0y8ZRL3crAQbbVJSavPlzOXLfpi98tVPpy+5+r0zdly61Y1EREREREREREREpNZUaknqvTS3E3AWtn0a0BRIUPCo0Ko8hQqtaq3ruI3zbhP93D1ycDfOGdguwUaJLV5TZH/295JfvoutuOrug7u/v80TiIiIiIiIiIiIiEi1qdSS1HhpbjZwIHAOFa/KAhVaiXJXmkKFVrXWddzGebeJfu66NK3LXxftTFaw5n8c2jZM+mfZsm9jy58Z99uCa765cNDaGk8mIiIiIiIiIiIiIo5UaklyvTS3CfB/wPlAW6AaBY8KrcpTqNCq1rqO2zjvdks/d2OO7c2R27VIsOG2m7V8fekXM5Z+8U10+fkPH9FrctImFhEREREREREREfE5lVqSHC/P64FtXwicBOSXj6vQUqG1+XYuKrR2ateQb88ZkGDD2llTWMIn0xZP//qfZaPuOLjH2JTsRERERERERERERMRHVGpJ7bw8bx/gMmx7aJVlKrRUaG2+nYsKLYDxp/djz3DjBBsnh23DpMjSZV/+s+yBGz+YfuO6u4eXpnSHIiIiIiIiIiIiIh6lUku23Svzg9j2EcCVwI7VLkJUaG1hHyq0qrWu4zbOu93az90BXZvyzsnbJ9g4Nf6ct2rDp9MWP/ve5IUXfXjuzuvTunMRERERERERERGRDKdSS6rvlfl5wMnY9uVAZ6D6RYgKrS3sQ4VWtdZ13MZ5t1v7ucsmwI//3ok+reonmCC1IkvWFn/816I3P5m66JyxZwxYbCSEiIiIiIiIiIiISIZRqSVb98r8fOAs4Epsu1X5uAotFVqJtq847rJCK2jDiTu05tmjeiaYIH3mrVxf+tHkRR9/88/SUx47bvv5pvOIiIiIiIiIiIiIuJlKLUns1QX52Ha8zIJWWy2cVGip0Np8OxcWWnnZQaZcMgiroE6CSdJv4aoN9vt/Lvzo23+WnvKoyi0RERERERERERERRyq1pKpXF8SvzLLteJkFWy+cVGip0Np8OxcWWgAX72Zx1/5dEkxi1qJVG+wP/lz40XeRpac8dLzKLREREREREREREZGKVGrJJq8uyAZOA67FttuWj6vQYuuljAqtSuMuLbQK6mQz7bJdaFo3J8FE7rB49YbSd36d/79PJy88+cX/22mV6TwiIiIiIiIiIiIibqBSS2D0wgC2/S/gJqBzwnJDhZYKrUTbVxx3aaEFcMt+nRgxuEOCidwntmRtybu/zX/2hUkzz/v62r03mM4jIiIiIiIiIiIiYpJKLb8bvXBfbPs/wPZA4nJDhZYKrUTbVxx3caHVpkEu0y/flTrZwQSTudeUeas2fPj7/LvveHfK1fMeODjRV0NERERERERERETE01Rq+dXohb2AO7Ht4eVjKrRUaG0pQwYXWtg2/z28B6cPaEsm+3HmshWf/DH/ghEH9XzedBYRERERERERERGRdFOp5TejF7YEbgDOwLazysdVaKnQ2lKGDC+0erSox68X7kxWMPP/yCsptfn0zwWRiX8tPObmf/X93nQeERERERERERERkXTJ/DO8Uj2jF+YClwCjgAbVKrFUaKnQSrR9xXGXF1oAr5/Yl0N6Nk8wYWZas6GYd3+eO2HSlEVHPHhq/8Wm84iIiIiIiIiIiIikmkotPxi9cBjwANAFqF6JpUJLhVai7SuOZ0ChtWuoERPOHpBgwsw3Z+naknd+mnPf7W9Ovnzmw4cm+sqJiIiIiIiIiIiIZDyVWl42emEYuA84qHxMhdZmyx3GEuVVoVV5PAMKrSAw/qz+7NqhIMGk3vHd30uWff77/JNHHL7d26aziIiIiIiIiIiIiKSCSi0vem1RHrY9EhgB5JWPq9DabLnDWKK8KrQqj2dIoXVQj+a8flLfBJN6T2FxKe//POeH76ctPvCWE/otMJ1HREREREREREREJJlUannNa4uGYNuPAV0rjavQ2my5w1iivCq0Ko9nSKGVFQzw84U706NFvQQTe9fcpetK3v1h9h3/N7TrVaaziIiIiIiIiIiIiCSLSi2veG1Rc+BubPvEKstUaG223GEsUV4VWpXHM6TQAjilfxueOKJngon9YdJfC+d+8du8g64+ZvufTGcRERERERERERERqS2VWl7w2qJTiBdaTaosU6G12XKHsUR5VWhVHs+gQis/J8jkS3elXaM8/G7l2iL77e9iYz79Ze7xz1y8R7HpPCIiIiIiIiIiIiI1pVIrk722yAIeA4ZvU6GhQqvyGxVaniq0AC7bI8Rtw7skmNyffpu5bNWnv8w96pLDtvvQdBYRERERERERERGRmlCplYnGLA5g22cAdwMNVGhVZy6HsUR5VWhVHs+wQqtxnRymX7krjepkJ9iBf63bUMy7381655XPZxzx+nX7FprOIyIiIiIiIiIiIrItVGplmjGLO2LbTwFDgG0rNFRoVX6jQstzhRY23LF/Fy7ZI5RgBwLwxz9LV3/ww+xjLj+677ums4iIiIiIiIiIiIhUl0qtTDJm8WnY9n1AA0CFVrXmchhLlFeFVuXxDCy02jeqw+TLd6FOdqUl4mDdhmI++H7WW+9/N+uIJy7dU8/aEhEREREREREREddTqZUJxixuDjyBbR9SPqZCS4XWFrP6r9ACeOqonpy0Y5sEOxEnv85YsvzTH2fvf+m/tv/adBYRERERERERERGRLVGp5XZjFh8EPIlttygfU6GlQmuLWf1ZaPVqWZ+fL9qZgP5U22Yr1xTab07659GTh3U/z3QWERERERERERERkUR0+tetxizOB+4FzqpRMbWl11tarkJrC/tQoVWtdR23cd5tsgotgDdP7ssBPZon2JFUx4Rf58a++XPBblce32+W6SwiIiIiIiIiIiIim1Op5UZjFm8HvAr0UqFVzXVVaPm60NqtQwGfn90/wY5kW8xbsrb4va9nnnvGQb2eMJ1FREREREREREREpCKVWm4zZvFZwH1AHRVa1VxXhZavC60ANhPP3YmBVqMEO5NtVVRcyrtfRz/86NvogY9ePqTYdB4RERERERERERER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\";\r\n","import { createReactBlockSpec } from \"@blocknote/react\";\r\nimport React, { useState, useCallback, useRef, useEffect } from \"react\";\r\n\r\nconst MIN_VIDEO_WIDTH = 200;\r\nconst MAX_VIDEO_WIDTH = 1200;\r\nconst MIN_VIDEO_HEIGHT = 120;\r\nconst MAX_VIDEO_HEIGHT = 600;\r\nconst DEFAULT_VIDEO_WIDTH = 640;\r\nconst DEFAULT_VIDEO_HEIGHT = 360;\r\n\r\ntype ResizeParams = {\r\n handleUsed: \"left\" | \"right\" | \"bottom\";\r\n initialClientX: number;\r\n initialClientY: number;\r\n initialWidth: number;\r\n initialHeight: number;\r\n};\r\n\r\n/**\r\n * 비디오 블록 카드: 우클릭 방지 + 다운로드 버튼 미노출\r\n * - controlsList=\"nodownload\": 브라우저 기본 컨트롤에서 다운로드 버튼 숨김\r\n * - onContextMenu preventDefault: 우클릭 컨텍스트 메뉴(동영상 저장 등) 차단\r\n */\r\nconst VideoBlockCard = ({\r\n url,\r\n editable,\r\n width,\r\n onWidthChange,\r\n height,\r\n onHeightChange,\r\n onContextMenuBlock,\r\n}: {\r\n url: string;\r\n editable: boolean;\r\n width?: number;\r\n onWidthChange?: (width: number) => void;\r\n height?: number;\r\n onHeightChange?: (height: number) => void;\r\n onContextMenuBlock: (e: React.MouseEvent) => void;\r\n}) => {\r\n const [resizeParams, setResizeParams] = useState<ResizeParams | undefined>(\r\n undefined,\r\n );\r\n const [localWidth, setLocalWidth] = useState<number | undefined>(width);\r\n const [localHeight, setLocalHeight] = useState<number | undefined>(height);\r\n const wrapperRef = useRef<HTMLDivElement>(null);\r\n\r\n useEffect(() => {\r\n setLocalWidth(width);\r\n }, [width]);\r\n useEffect(() => {\r\n setLocalHeight(height);\r\n }, [height]);\r\n\r\n useEffect(() => {\r\n if (!resizeParams) return;\r\n\r\n const onMouseMove = (e: MouseEvent) => {\r\n if (resizeParams.handleUsed === \"bottom\") {\r\n const delta = e.clientY - resizeParams.initialClientY;\r\n setLocalHeight(\r\n Math.min(\r\n Math.max(resizeParams.initialHeight + delta, MIN_VIDEO_HEIGHT),\r\n MAX_VIDEO_HEIGHT,\r\n ),\r\n );\r\n } else {\r\n const delta =\r\n resizeParams.handleUsed === \"left\"\r\n ? resizeParams.initialClientX - e.clientX\r\n : e.clientX - resizeParams.initialClientX;\r\n setLocalWidth(\r\n Math.min(\r\n Math.max(resizeParams.initialWidth + delta, MIN_VIDEO_WIDTH),\r\n wrapperRef.current?.parentElement?.clientWidth || MAX_VIDEO_WIDTH,\r\n ),\r\n );\r\n }\r\n };\r\n\r\n const onMouseUp = () => {\r\n const handle = resizeParams.handleUsed;\r\n setResizeParams(undefined);\r\n if (handle === \"bottom\") {\r\n if (localHeight != null && onHeightChange) onHeightChange(localHeight);\r\n } else {\r\n if (localWidth != null && onWidthChange) onWidthChange(localWidth);\r\n }\r\n };\r\n\r\n window.addEventListener(\"mousemove\", onMouseMove);\r\n window.addEventListener(\"mouseup\", onMouseUp);\r\n return () => {\r\n window.removeEventListener(\"mousemove\", onMouseMove);\r\n window.removeEventListener(\"mouseup\", onMouseUp);\r\n };\r\n }, [resizeParams, localWidth, localHeight, onWidthChange, onHeightChange]);\r\n\r\n const handleLeftDown = useCallback(\r\n (e: React.MouseEvent) => {\r\n e.preventDefault();\r\n e.stopPropagation();\r\n setResizeParams({\r\n handleUsed: \"left\",\r\n initialWidth: wrapperRef.current?.clientWidth ?? DEFAULT_VIDEO_WIDTH,\r\n initialHeight: localHeight ?? DEFAULT_VIDEO_HEIGHT,\r\n initialClientX: e.clientX,\r\n initialClientY: e.clientY,\r\n });\r\n },\r\n [localHeight],\r\n );\r\n\r\n const handleRightDown = useCallback(\r\n (e: React.MouseEvent) => {\r\n e.preventDefault();\r\n e.stopPropagation();\r\n setResizeParams({\r\n handleUsed: \"right\",\r\n initialWidth: wrapperRef.current?.clientWidth ?? DEFAULT_VIDEO_WIDTH,\r\n initialHeight: localHeight ?? DEFAULT_VIDEO_HEIGHT,\r\n initialClientX: e.clientX,\r\n initialClientY: e.clientY,\r\n });\r\n },\r\n [localHeight],\r\n );\r\n\r\n const handleBottomDown = useCallback(\r\n (e: React.MouseEvent) => {\r\n e.preventDefault();\r\n e.stopPropagation();\r\n setResizeParams({\r\n handleUsed: \"bottom\",\r\n initialWidth: wrapperRef.current?.clientWidth ?? DEFAULT_VIDEO_WIDTH,\r\n initialHeight: localHeight ?? DEFAULT_VIDEO_HEIGHT,\r\n initialClientX: e.clientX,\r\n initialClientY: e.clientY,\r\n });\r\n },\r\n [localHeight],\r\n );\r\n\r\n const resizeCursor = resizeParams\r\n ? resizeParams.handleUsed === \"bottom\"\r\n ? \"ns-resize\"\r\n : \"ew-resize\"\r\n : \"default\";\r\n\r\n const [hovered, setHovered] = useState(false);\r\n\r\n return (\r\n <div\r\n ref={wrapperRef}\r\n className=\"lumir-video-block-wrapper\"\r\n onContextMenu={onContextMenuBlock}\r\n style={{\r\n position: \"relative\",\r\n width: localWidth != null ? `${localWidth}px` : undefined,\r\n maxWidth: \"100%\",\r\n cursor: resizeCursor,\r\n transition: resizeParams ? \"none\" : \"box-shadow 0.2s\",\r\n }}\r\n onMouseEnter={() => {\r\n if (!resizeParams) setHovered(true);\r\n }}\r\n onMouseLeave={() => {\r\n if (!resizeParams) setHovered(false);\r\n }}\r\n >\r\n {editable && (hovered || resizeParams) && (\r\n <>\r\n <div\r\n className=\"lumir-resize-handle\"\r\n style={{ left: \"4px\" }}\r\n onMouseDown={handleLeftDown}\r\n />\r\n <div\r\n className=\"lumir-resize-handle\"\r\n style={{ right: \"4px\" }}\r\n onMouseDown={handleRightDown}\r\n />\r\n </>\r\n )}\r\n\r\n <div\r\n style={{\r\n width: \"100%\",\r\n height: `${localHeight ?? DEFAULT_VIDEO_HEIGHT}px`,\r\n overflow: \"hidden\",\r\n backgroundColor: \"#000\",\r\n borderRadius: \"6px\",\r\n }}\r\n >\r\n <video\r\n src={url}\r\n controls\r\n controlsList=\"nodownload\"\r\n playsInline\r\n style={{\r\n width: \"100%\",\r\n height: \"100%\",\r\n objectFit: \"contain\",\r\n display: \"block\",\r\n }}\r\n onContextMenu={(e) => e.preventDefault()}\r\n />\r\n {editable && (hovered || resizeParams) && (\r\n <div\r\n className=\"lumir-resize-handle-bottom\"\r\n onMouseDown={handleBottomDown}\r\n />\r\n )}\r\n </div>\r\n </div>\r\n );\r\n};\r\n\r\nexport const VideoBlock = createReactBlockSpec(\r\n {\r\n type: \"video\",\r\n propSchema: {\r\n url: { default: \"\" },\r\n previewWidth: { default: 640 },\r\n previewHeight: { default: 360 },\r\n },\r\n content: \"none\",\r\n },\r\n {\r\n render: (props) => {\r\n const url = props.block.props.url ?? \"\";\r\n const editable = props.editor.isEditable;\r\n\r\n const handleContextMenu = useCallback((e: React.MouseEvent) => {\r\n e.preventDefault();\r\n e.stopPropagation();\r\n }, []);\r\n\r\n if (!url) {\r\n return (\r\n <div\r\n className=\"lumir-video-block-placeholder\"\r\n onContextMenu={handleContextMenu}\r\n style={{\r\n width: \"100%\",\r\n maxWidth: `${DEFAULT_VIDEO_WIDTH}px`,\r\n height: `${DEFAULT_VIDEO_HEIGHT}px`,\r\n backgroundColor: \"#1a1a1a\",\r\n borderRadius: \"6px\",\r\n display: \"flex\",\r\n alignItems: \"center\",\r\n justifyContent: \"center\",\r\n color: \"rgba(255,255,255,0.5)\",\r\n fontSize: \"14px\",\r\n }}\r\n >\r\n 비디오 URL 없음\r\n </div>\r\n );\r\n }\r\n\r\n return (\r\n <VideoBlockCard\r\n url={url}\r\n editable={editable}\r\n width={props.block.props.previewWidth}\r\n onWidthChange={(newWidth) => {\r\n props.editor.updateBlock(props.block, {\r\n props: { previewWidth: newWidth },\r\n });\r\n }}\r\n height={props.block.props.previewHeight}\r\n onHeightChange={(newHeight) => {\r\n props.editor.updateBlock(props.block, {\r\n props: { previewHeight: newHeight },\r\n });\r\n }}\r\n onContextMenuBlock={handleContextMenu}\r\n />\r\n );\r\n },\r\n },\r\n);\r\n","import { createStronglyTypedTiptapNode } from \"@blocknote/core\";\r\nimport { columnNormalization } from \"./columnNormalization\";\r\nimport { columnDnd } from \"./columnDnd\";\r\n\r\n/**\r\n * 다단(컬럼) 레이아웃의 컨테이너 노드 `columnList`.\r\n *\r\n * @blocknote/xl-multi-column(AGPL)을 쓰지 않고, 코어가 이미 인지하는 노드 구조\r\n * (core/src/pm-nodes/README.md, MIT)를 직접 정의한다(라이선스 안전).\r\n * - 코어 UniqueID가 [\"blockContainer\",\"columnList\",\"column\"]에 id 부여,\r\n * blockToNode가 bnBlock 그룹 노드를 {id,...props,children}로 변환,\r\n * getBlockInfoFromPos가 그룹 기반 처리.\r\n * - nodeToBlock(직렬화)은 blockSchema에 스펙이 있어야 인식하므로, 이 노드를\r\n * createBlockSpecFromStronglyTypedTiptapNode로 감싸 schema에 등록한다(HtmlPreview.tsx).\r\n *\r\n * 스키마(README):\r\n * group: \"childContainer bnBlock blockGroupChild\"\r\n * content: \"column column+\" (최소 2단)\r\n */\r\nexport const ColumnList = createStronglyTypedTiptapNode({\r\n name: \"columnList\",\r\n group: \"childContainer bnBlock blockGroupChild\",\r\n content: \"column column+\",\r\n\r\n addAttributes() {\r\n return {\r\n // 블록별 중앙 세로 구분선 표시 여부(드래그핸들 메뉴에서 토글). data-divider로 렌더.\r\n showDivider: {\r\n default: false,\r\n parseHTML: (element) => element.getAttribute(\"data-divider\") === \"true\",\r\n renderHTML: (attributes) =>\r\n attributes.showDivider ? { \"data-divider\": \"true\" } : {},\r\n },\r\n };\r\n },\r\n\r\n parseHTML() {\r\n return [{ tag: 'div[data-node-type=\"columnList\"]' }];\r\n },\r\n\r\n renderHTML({ HTMLAttributes }) {\r\n const dom = document.createElement(\"div\");\r\n dom.className = \"bn-column-list\";\r\n dom.setAttribute(\"data-node-type\", \"columnList\");\r\n for (const [attribute, value] of Object.entries(HTMLAttributes)) {\r\n if (attribute !== \"class\") {\r\n dom.setAttribute(attribute, value as string);\r\n }\r\n }\r\n return { dom, contentDOM: dom };\r\n },\r\n\r\n // columnNormalization: 빈 컬럼/1단 columnList 자동 복구(unwrap).\r\n // columnDnd: 블록을 다른 블록 좌/우로 드롭하면 2단 컬럼 생성.\r\n addProseMirrorPlugins() {\r\n return [columnNormalization(), columnDnd()];\r\n },\r\n});\r\n","import { Plugin, PluginKey } from \"prosemirror-state\";\r\n\r\nexport const columnNormalizationKey = new PluginKey(\"lumirColumnNormalization\");\r\n\r\n/**\r\n * columnList 불변식을 유지하는 정규화 플러그인.\r\n *\r\n * 문제: 코어 removeBlocks/일반 편집은 column/columnList의 최소 content 규칙을 강제하지\r\n * 않는다 → 컬럼의 마지막 블록을 지우면 **빈 column**(content \"blockContainer+\" 위반),\r\n * 컬럼을 통째로 지우면 **1단 columnList**(content \"column column+\" 위반)가 되어 문서가\r\n * 깨지고 직렬화(nodeToBlock)가 오류날 수 있다.\r\n *\r\n * 해결: appendTransaction에서 매 docChanged마다 columnList를 검사해,\r\n * - 내용 있는 컬럼이 2개 미만이면 columnList를 **풀어서**(unwrap) 남은 컬럼들의\r\n * blockContainer들을 부모 blockGroup에 직접 펼친다(blockContainer는 blockGroupChild라 유효).\r\n * - 내용이 전혀 없으면 columnList를 제거한다.\r\n * 정규화 후에는 더 고칠 게 없어 self-terminating.\r\n */\r\nexport function columnNormalization(): Plugin {\r\n return new Plugin({\r\n key: columnNormalizationKey,\r\n appendTransaction(transactions, _oldState, newState) {\r\n if (!transactions.some((tr) => tr.docChanged)) {\r\n return null;\r\n }\r\n\r\n const lists: { pos: number; node: any }[] = [];\r\n newState.doc.descendants((node: any, pos: number) => {\r\n if (node.type.name === \"columnList\") {\r\n lists.push({ pos, node });\r\n return false; // columnList는 중첩되지 않음 → 하위 순회 불필요\r\n }\r\n return undefined;\r\n });\r\n if (lists.length === 0) {\r\n return null;\r\n }\r\n\r\n const tr = newState.tr;\r\n let modified = false;\r\n\r\n // 위치 큰 것부터 처리해 앞쪽 위치가 어긋나지 않게 한다.\r\n for (let i = lists.length - 1; i >= 0; i--) {\r\n const { pos, node } = lists[i];\r\n\r\n const nonEmptyColumns: any[] = [];\r\n node.forEach((col: any) => {\r\n if (col.type.name === \"column\" && col.childCount > 0) {\r\n nonEmptyColumns.push(col);\r\n }\r\n });\r\n\r\n if (nonEmptyColumns.length >= 2) {\r\n continue; // 유효한 다단 → 유지\r\n }\r\n\r\n const from = tr.mapping.map(pos);\r\n const to = tr.mapping.map(pos + node.nodeSize);\r\n\r\n // 남은 컬럼들의 blockContainer들을 모아 columnList를 대체(unwrap).\r\n const blocks: any[] = [];\r\n nonEmptyColumns.forEach((col: any) => {\r\n col.forEach((bc: any) => blocks.push(bc));\r\n });\r\n\r\n if (blocks.length > 0) {\r\n tr.replaceWith(from, to, blocks);\r\n } else {\r\n tr.delete(from, to);\r\n }\r\n modified = true;\r\n }\r\n\r\n return modified ? tr : null;\r\n },\r\n });\r\n}\r\n","import { Plugin, PluginKey } from \"prosemirror-state\";\r\nimport type { EditorView } from \"prosemirror-view\";\r\nimport { Decoration, DecorationSet } from \"prosemirror-view\";\r\nimport { mergeBlocksIntoColumns } from \"./mergeColumns\";\r\n\r\nexport const columnDndKey = new PluginKey<DndState>(\"lumirColumnDnd\");\r\n\r\ntype DndState = {\r\n /** 대상 top-level blockContainer의 pos(없으면 null). */\r\n targetPos: number | null;\r\n side: \"left\" | \"right\" | null;\r\n};\r\n\r\n/** 가장자리로 인식할 폭(px). 블록 폭의 30%와 80px 중 작은 값. */\r\nfunction edgeThreshold(width: number): number {\r\n return Math.min(80, width * 0.3);\r\n}\r\n\r\n/**\r\n * 좌표 아래의 **top-level** blockContainer(루트 blockGroup 직속)를 찾는다.\r\n * v1은 최상위 블록만 컬럼 생성 대상으로 허용(컬럼 내부/중첩 블록은 제외).\r\n */\r\nfunction topLevelBlockAtCoords(\r\n view: EditorView,\r\n x: number,\r\n y: number,\r\n): { pos: number; nodeSize: number; id: string; dom: HTMLElement } | null {\r\n const found = view.posAtCoords({ left: x, top: y });\r\n if (!found) {\r\n return null;\r\n }\r\n const $pos = view.state.doc.resolve(found.pos);\r\n for (let d = $pos.depth; d > 0; d--) {\r\n if ($pos.node(d).type.name === \"blockContainer\") {\r\n // 루트 blockGroup(depth 1) 직속만 → blockContainer는 depth 2.\r\n if (d !== 2 || $pos.node(d - 1).type.name !== \"blockGroup\") {\r\n return null;\r\n }\r\n const pos = $pos.before(d);\r\n const node = $pos.node(d);\r\n const dom = view.nodeDOM(pos) as HTMLElement | null;\r\n if (!dom || !node.attrs?.id) {\r\n return null;\r\n }\r\n return { pos, nodeSize: node.nodeSize, id: node.attrs.id, dom };\r\n }\r\n }\r\n return null;\r\n}\r\n\r\n/** NodeSelection이면 드래그된 단일 블록 id를 반환(다중 선택은 v1 미지원 → null). */\r\nfunction draggedBlockId(view: EditorView): string | null {\r\n const sel: any = view.state.selection;\r\n // MultipleNodeSelection 등은 node가 없음 → v1 단일만.\r\n return sel?.node?.attrs?.id ?? null;\r\n}\r\n\r\nfunction setDnd(view: EditorView, next: DndState) {\r\n const cur = columnDndKey.getState(view.state);\r\n if (cur && cur.targetPos === next.targetPos && cur.side === next.side) {\r\n return; // 변화 없음 → dispatch 생략(드래그오버 스팸 방지)\r\n }\r\n view.dispatch(view.state.tr.setMeta(columnDndKey, next));\r\n}\r\n\r\nconst CLEARED: DndState = { targetPos: null, side: null };\r\n\r\n/**\r\n * 블록을 다른 블록의 좌/우 가장자리로 드롭하면 2단 컬럼을 생성하는 DnD 플러그인.\r\n * - dragover: 대상 블록의 좌/우 가장자리 hit-test → 세로 인디케이터(데코) + 기본 드롭커서 숨김.\r\n * - drop(handleDrop): 가장자리 드롭이면 mergeBlocksIntoColumns로 래핑하고 기본 동작 취소.\r\n */\r\nexport function columnDnd(): Plugin<DndState> {\r\n return new Plugin<DndState>({\r\n key: columnDndKey,\r\n state: {\r\n init: () => CLEARED,\r\n apply(tr, prev) {\r\n const meta = tr.getMeta(columnDndKey);\r\n if (meta) {\r\n return meta as DndState;\r\n }\r\n // 문서가 바뀌면 좌표 기반 targetPos가 무효 → 초기화.\r\n if (tr.docChanged && prev.targetPos !== null) {\r\n return CLEARED;\r\n }\r\n return prev;\r\n },\r\n },\r\n props: {\r\n handleDOMEvents: {\r\n dragover: (view, event) => {\r\n if (!(view as any).dragging) {\r\n return false;\r\n }\r\n const target = topLevelBlockAtCoords(\r\n view,\r\n event.clientX,\r\n event.clientY,\r\n );\r\n const draggedId = draggedBlockId(view);\r\n if (!target || !draggedId || target.id === draggedId) {\r\n setDnd(view, CLEARED);\r\n view.dom.classList.remove(\"lumir-col-dnd-active\");\r\n return false;\r\n }\r\n const rect = target.dom.getBoundingClientRect();\r\n const th = edgeThreshold(rect.width);\r\n let side: \"left\" | \"right\" | null = null;\r\n if (event.clientX - rect.left <= th) {\r\n side = \"left\";\r\n } else if (rect.right - event.clientX <= th) {\r\n side = \"right\";\r\n }\r\n if (side) {\r\n setDnd(view, { targetPos: target.pos, side });\r\n view.dom.classList.add(\"lumir-col-dnd-active\");\r\n } else {\r\n setDnd(view, CLEARED);\r\n view.dom.classList.remove(\"lumir-col-dnd-active\");\r\n }\r\n return false;\r\n },\r\n dragend: (view) => {\r\n setDnd(view, CLEARED);\r\n view.dom.classList.remove(\"lumir-col-dnd-active\");\r\n return false;\r\n },\r\n dragleave: (view, event) => {\r\n // 에디터 밖으로 나가면 정리(내부 이동은 무시).\r\n if (!(event as DragEvent).relatedTarget) {\r\n setDnd(view, CLEARED);\r\n view.dom.classList.remove(\"lumir-col-dnd-active\");\r\n }\r\n return false;\r\n },\r\n },\r\n handleDrop: (view, _event, _slice, _moved) => {\r\n const st = columnDndKey.getState(view.state);\r\n view.dom.classList.remove(\"lumir-col-dnd-active\");\r\n if (!st || st.side === null || st.targetPos === null) {\r\n return false; // 일반(상하) 드롭 → 기본 처리\r\n }\r\n const draggedId = draggedBlockId(view);\r\n const targetNode = view.state.doc.nodeAt(st.targetPos);\r\n const targetId = targetNode?.attrs?.id as string | undefined;\r\n // 상태 초기화(드롭 후 잔여 인디케이터 제거)\r\n view.dispatch(view.state.tr.setMeta(columnDndKey, CLEARED));\r\n if (!draggedId || !targetId) {\r\n return false;\r\n }\r\n const editor = (view as any).__lumirEditor;\r\n if (!editor) {\r\n return false;\r\n }\r\n const ok = mergeBlocksIntoColumns(editor, draggedId, targetId, st.side);\r\n return ok; // 성공 시 기본 드롭 취소\r\n },\r\n decorations: (state) => {\r\n const st = columnDndKey.getState(state);\r\n if (!st || st.side === null || st.targetPos === null) {\r\n return null;\r\n }\r\n const node = state.doc.nodeAt(st.targetPos);\r\n if (!node) {\r\n return null;\r\n }\r\n return DecorationSet.create(state.doc, [\r\n Decoration.node(st.targetPos, st.targetPos + node.nodeSize, {\r\n class:\r\n st.side === \"left\"\r\n ? \"lumir-col-drop-left\"\r\n : \"lumir-col-drop-right\",\r\n }),\r\n ]);\r\n },\r\n },\r\n });\r\n}\r\n","/**\r\n * 드래그된 블록과 대상 블록을 2단 columnList로 병합한다(DnD 컬럼 생성의 핵심 로직).\r\n *\r\n * 공개 블록 API(getBlock/removeBlocks/replaceBlocks)만 사용하므로 columnList/column이\r\n * 블록 스펙으로 등록돼 있어야 한다(이미 등록됨). 이벤트 처리(columnDnd 플러그인)와 분리해\r\n * 단독 테스트가 가능하다.\r\n *\r\n * @param side \"left\" → [dragged | target], \"right\" → [target | dragged]\r\n * @returns 병합 성공 여부\r\n */\r\nexport function mergeBlocksIntoColumns(\r\n editor: any,\r\n draggedId: string,\r\n targetId: string,\r\n side: \"left\" | \"right\",\r\n): boolean {\r\n if (!editor || !draggedId || !targetId || draggedId === targetId) {\r\n return false;\r\n }\r\n\r\n const dragged = editor.getBlock?.(draggedId);\r\n const target = editor.getBlock?.(targetId);\r\n if (!dragged || !target) {\r\n return false;\r\n }\r\n // 컬럼/컬럼리스트 자체는 드래그 대상에서 제외(중첩/해체 복잡도 회피, v1).\r\n if (\r\n dragged.type === \"columnList\" ||\r\n dragged.type === \"column\" ||\r\n target.type === \"columnList\" ||\r\n target.type === \"column\"\r\n ) {\r\n return false;\r\n }\r\n\r\n const columnList = {\r\n type: \"columnList\",\r\n children:\r\n side === \"left\"\r\n ? [\r\n { type: \"column\", children: [dragged] },\r\n { type: \"column\", children: [target] },\r\n ]\r\n : [\r\n { type: \"column\", children: [target] },\r\n { type: \"column\", children: [dragged] },\r\n ],\r\n };\r\n\r\n try {\r\n const run = () => {\r\n // 원본에서 드래그 블록 제거 후, 대상을 columnList로 교체(대상+드래그를 단으로).\r\n editor.removeBlocks([draggedId]);\r\n editor.replaceBlocks([targetId], [columnList]);\r\n };\r\n if (typeof editor.transact === \"function\") {\r\n editor.transact(run);\r\n } else {\r\n run();\r\n }\r\n return true;\r\n } catch {\r\n return false;\r\n }\r\n}\r\n","import { createStronglyTypedTiptapNode } from \"@blocknote/core\";\r\n\r\n/**\r\n * 다단 레이아웃의 단일 컬럼 노드 `column`.\r\n *\r\n * 스키마(core/src/pm-nodes/README.md, MIT):\r\n * group: \"bnBlock childContainer\"\r\n * content: \"blockContainer+\" (컬럼은 1개 이상의 블록을 담는다)\r\n *\r\n * MVP은 모든 컬럼을 균등 폭(flex:1)으로 렌더한다. 향후 너비 리사이즈를 위해 선택적\r\n * `width` attr 자리만 둔다(현재 CSS 미사용).\r\n */\r\nexport const Column = createStronglyTypedTiptapNode({\r\n name: \"column\",\r\n group: \"bnBlock childContainer\",\r\n content: \"blockContainer+\",\r\n\r\n addAttributes() {\r\n return {\r\n width: {\r\n default: 1,\r\n parseHTML: (element) => {\r\n const w = parseFloat(element.getAttribute(\"data-column-width\") || \"\");\r\n return Number.isFinite(w) && w > 0 ? w : 1;\r\n },\r\n renderHTML: (attributes) => {\r\n if (!attributes.width || attributes.width === 1) {\r\n return {};\r\n }\r\n return { \"data-column-width\": String(attributes.width) };\r\n },\r\n },\r\n };\r\n },\r\n\r\n parseHTML() {\r\n return [{ tag: 'div[data-node-type=\"column\"]' }];\r\n },\r\n\r\n renderHTML({ HTMLAttributes }) {\r\n const dom = document.createElement(\"div\");\r\n dom.className = \"bn-column\";\r\n dom.setAttribute(\"data-node-type\", \"column\");\r\n for (const [attribute, value] of Object.entries(HTMLAttributes)) {\r\n if (attribute !== \"class\") {\r\n dom.setAttribute(attribute, value as string);\r\n }\r\n }\r\n return { dom, contentDOM: dom };\r\n },\r\n});\r\n","\"use client\";\r\n\r\nimport { createStyleSpec } from \"@blocknote/core\";\r\n\r\n/**\r\n * 인라인 글자 크기 커스텀 스타일.\r\n *\r\n * - propSchema \"string\": 값으로 \"18px\" 같은 CSS 크기를 가진다.\r\n * - HTML 직렬화: `<span data-style-type=\"fontSize\" data-value=\"18px\" style=\"font-size:18px\">`\r\n * (BlockNote가 data-* 속성과 파싱 규칙을 자동 생성 → 신버전 에디터 간 복사/붙여넣기 왕복 보장,\r\n * 구버전 에디터는 매칭 파싱 규칙이 없어 조용히 무시)\r\n *\r\n * ⚠️ 저장 JSON 하위호환: BlockNote는 styles 맵에 스키마에 없는 키가 있으면\r\n * `style ... not found in styleSchema` 예외를 던지므로, 저장 JSON에는\r\n * styles.fontSize 대신 형제 키 fontSize로 직렬화한다.\r\n * → src/utils/font-size-serialization.ts (liftFontSize/lowerFontSize) 참고.\r\n */\r\nexport const FontSize = createStyleSpec(\r\n {\r\n type: \"fontSize\",\r\n propSchema: \"string\",\r\n },\r\n {\r\n // 바닐라(동기) 스타일 스펙: DOM을 직접 반환한다.\r\n // createReactStyleSpec는 span 내용이 비동기 렌더되어, 적용 직후 동기 시점에\r\n // 선택 영역의 DOM 좌표가 (0,0)으로 잡혀 BlockNote 포매팅 툴바가 좌상단으로\r\n // 튀는 문제가 있었다(굵게/기울임 등 네이티브 마크는 동기 렌더라 정상).\r\n render: (value) => {\r\n const span = document.createElement(\"span\");\r\n span.style.fontSize = value;\r\n return { dom: span, contentDOM: span };\r\n },\r\n },\r\n);\r\n\r\n/** 툴바 드롭다운에서 제공하는 프리셋 (본문 기본은 14px = \"기본\") */\r\nexport const FONT_SIZE_PRESETS = [\r\n \"10px\",\r\n \"12px\",\r\n \"14px\",\r\n \"16px\",\r\n \"18px\",\r\n \"20px\",\r\n \"24px\",\r\n \"28px\",\r\n] as const;\r\n\r\nexport type FontSizePreset = (typeof FONT_SIZE_PRESETS)[number];\r\n\r\n/** 글자 크기 1px 스테퍼의 허용 범위/기본값 (사용자 결정: 8~96px). */\r\nexport const FONT_SIZE_MIN = 8;\r\nexport const FONT_SIZE_MAX = 96;\r\n/** \"기본\"(명시 fontSize 없음)일 때 기준이 되는 본문 px. */\r\nexport const FONT_SIZE_DEFAULT_PX = 14;\r\nexport const FONT_SIZE_STEP = 1;\r\n\r\n/** \"18px\" → 18, \"\" / 파싱 실패 → 14(기본). */\r\nexport function parseFontSizePx(size: string): number {\r\n const n = parseInt(size, 10);\r\n return Number.isFinite(n) ? n : FONT_SIZE_DEFAULT_PX;\r\n}\r\n\r\n/** 8~96 범위로 보정 + 정수 반올림. */\r\nexport function clampFontSizePx(px: number): number {\r\n return Math.min(FONT_SIZE_MAX, Math.max(FONT_SIZE_MIN, Math.round(px)));\r\n}\r\n\r\n/** 숫자 → \"Npx\" (clamp 포함). */\r\nexport function toFontSizeValue(px: number): string {\r\n return `${clampFontSizePx(px)}px`;\r\n}\r\n","\"use client\";\r\n\r\nimport React, { useState, useEffect, useRef } from \"react\";\r\nimport type { EditorType } from \"../../types\";\r\nimport { cn } from \"../../utils/cn\";\r\nimport { Icons } from \"./Icons\";\r\nimport {\r\n ToolbarDivider,\r\n UndoRedoButtons,\r\n TextStyleButton,\r\n AlignButton,\r\n ListButton,\r\n ImageButton,\r\n ColorButton,\r\n FontSizeButton,\r\n LinkButton,\r\n TableButton,\r\n HTMLImportButton,\r\n BlockTypeSelect,\r\n} from \"./components\";\r\n\r\n// 반응형 브레이크포인트 (px)\r\nconst COMPACT_BREAKPOINT = 700;\r\nconst MINIMIZED_BREAKPOINT = 400;\r\n\r\nexport interface FloatingMenuProps {\r\n editor: EditorType | any;\r\n position?: \"sticky\" | \"fixed\";\r\n className?: string;\r\n onImageUpload?: () => void;\r\n}\r\n\r\n/**\r\n * FloatingMenu - 에디터 상단 고정 툴바\r\n */\r\nexport const FloatingMenu: React.FC<FloatingMenuProps> = ({\r\n editor,\r\n position = \"sticky\",\r\n className,\r\n onImageUpload,\r\n}) => {\r\n const wrapperRef = useRef<HTMLDivElement>(null);\r\n const [isCompact, setIsCompact] = useState(false);\r\n const [isMinimizable, setIsMinimizable] = useState(false);\r\n const [isMinimized, setIsMinimized] = useState(false);\r\n // 선택 변경 시 리렌더링을 위한 카운터\r\n const [, setSelectionTick] = useState(0);\r\n\r\n // 선택 변경 감지 - 스타일 버튼 상태 업데이트를 위해 (디바운싱 적용)\r\n useEffect(() => {\r\n if (!editor) return;\r\n\r\n let debounceTimer: ReturnType<typeof setTimeout> | null = null;\r\n const DEBOUNCE_DELAY = 150;\r\n\r\n const handleSelectionChange = () => {\r\n if (debounceTimer) {\r\n clearTimeout(debounceTimer);\r\n }\r\n debounceTimer = setTimeout(() => {\r\n setSelectionTick((prev) => prev + 1);\r\n }, DEBOUNCE_DELAY);\r\n };\r\n\r\n const unsubscribe = editor.onSelectionChange?.(handleSelectionChange);\r\n const unsubscribeContent = editor.onEditorContentChange?.(() => {\r\n setSelectionTick((prev) => prev + 1);\r\n });\r\n\r\n return () => {\r\n if (debounceTimer) {\r\n clearTimeout(debounceTimer);\r\n }\r\n unsubscribe?.();\r\n unsubscribeContent?.();\r\n };\r\n }, [editor]);\r\n\r\n // 컨테이너 너비 감지\r\n useEffect(() => {\r\n const checkWidth = () => {\r\n if (wrapperRef.current) {\r\n const width = wrapperRef.current.offsetWidth;\r\n setIsCompact(width < COMPACT_BREAKPOINT);\r\n setIsMinimizable(width < MINIMIZED_BREAKPOINT);\r\n }\r\n };\r\n\r\n checkWidth();\r\n\r\n const resizeObserver = new ResizeObserver(checkWidth);\r\n if (wrapperRef.current) {\r\n resizeObserver.observe(wrapperRef.current);\r\n }\r\n\r\n return () => resizeObserver.disconnect();\r\n }, []);\r\n\r\n // 최소화된 레이아웃 (400px 이하)\r\n const MinimizedLayout = () => (\r\n <>\r\n <button\r\n className=\"lumir-toolbar-button lumir-toggle-button\"\r\n onClick={() => setIsMinimized(!isMinimized)}\r\n onMouseDown={(e) => e.preventDefault()}\r\n type=\"button\"\r\n title={isMinimized ? \"메뉴 펼치기\" : \"메뉴 접기\"}\r\n >\r\n {isMinimized ? Icons.chevronRight : Icons.chevronLeft}\r\n </button>\r\n {!isMinimized && (\r\n <>\r\n <ToolbarDivider />\r\n <UndoRedoButtons editor={editor} />\r\n <ToolbarDivider />\r\n <div className=\"lumir-toolbar-group\">\r\n <BlockTypeSelect editor={editor} />\r\n </div>\r\n <ToolbarDivider />\r\n <div className=\"lumir-toolbar-group\">\r\n <TextStyleButton editor={editor} style=\"bold\" />\r\n <TextStyleButton editor={editor} style=\"italic\" />\r\n <TextStyleButton editor={editor} style=\"underline\" />\r\n <TextStyleButton editor={editor} style=\"strike\" />\r\n </div>\r\n <ToolbarDivider />\r\n <div className=\"lumir-toolbar-group\">\r\n <AlignButton editor={editor} alignment=\"left\" />\r\n <AlignButton editor={editor} alignment=\"center\" />\r\n <AlignButton editor={editor} alignment=\"right\" />\r\n </div>\r\n <ToolbarDivider />\r\n <div className=\"lumir-toolbar-group\">\r\n <ListButton editor={editor} type=\"bullet\" />\r\n <ListButton editor={editor} type=\"numbered\" />\r\n </div>\r\n <ToolbarDivider />\r\n <div className=\"lumir-toolbar-group\">\r\n <FontSizeButton editor={editor} />\r\n <ColorButton editor={editor} type=\"text\" />\r\n <ColorButton editor={editor} type=\"background\" />\r\n </div>\r\n <ToolbarDivider />\r\n <div className=\"lumir-toolbar-group\">\r\n <ImageButton editor={editor} onImageUpload={onImageUpload} />\r\n <LinkButton editor={editor} />\r\n <TableButton editor={editor} />\r\n <HTMLImportButton editor={editor} />\r\n </div>\r\n </>\r\n )}\r\n </>\r\n );\r\n\r\n // 1단 레이아웃\r\n const SingleRowLayout = () => (\r\n <>\r\n <UndoRedoButtons editor={editor} />\r\n <ToolbarDivider />\r\n <div className=\"lumir-toolbar-group\">\r\n <BlockTypeSelect editor={editor} />\r\n </div>\r\n <ToolbarDivider />\r\n <div className=\"lumir-toolbar-group\">\r\n <TextStyleButton editor={editor} style=\"bold\" />\r\n <TextStyleButton editor={editor} style=\"italic\" />\r\n <TextStyleButton editor={editor} style=\"underline\" />\r\n <TextStyleButton editor={editor} style=\"strike\" />\r\n </div>\r\n <ToolbarDivider />\r\n <div className=\"lumir-toolbar-group\">\r\n <AlignButton editor={editor} alignment=\"left\" />\r\n <AlignButton editor={editor} alignment=\"center\" />\r\n <AlignButton editor={editor} alignment=\"right\" />\r\n </div>\r\n <ToolbarDivider />\r\n <div className=\"lumir-toolbar-group\">\r\n <ListButton editor={editor} type=\"bullet\" />\r\n <ListButton editor={editor} type=\"numbered\" />\r\n </div>\r\n <ToolbarDivider />\r\n <div className=\"lumir-toolbar-group\">\r\n <FontSizeButton editor={editor} />\r\n <ColorButton editor={editor} type=\"text\" />\r\n <ColorButton editor={editor} type=\"background\" />\r\n </div>\r\n <ToolbarDivider />\r\n <div className=\"lumir-toolbar-group\">\r\n <ImageButton editor={editor} onImageUpload={onImageUpload} />\r\n <LinkButton editor={editor} />\r\n <TableButton editor={editor} />\r\n <HTMLImportButton editor={editor} />\r\n </div>\r\n </>\r\n );\r\n\r\n // 2단 레이아웃\r\n const TwoRowLayout = () => (\r\n <>\r\n <div className=\"lumir-toolbar-row\">\r\n <UndoRedoButtons editor={editor} />\r\n <ToolbarDivider />\r\n <div className=\"lumir-toolbar-group\">\r\n <TextStyleButton editor={editor} style=\"bold\" />\r\n <TextStyleButton editor={editor} style=\"italic\" />\r\n <TextStyleButton editor={editor} style=\"underline\" />\r\n <TextStyleButton editor={editor} style=\"strike\" />\r\n </div>\r\n <ToolbarDivider />\r\n <div className=\"lumir-toolbar-group\">\r\n <AlignButton editor={editor} alignment=\"left\" />\r\n <AlignButton editor={editor} alignment=\"center\" />\r\n <AlignButton editor={editor} alignment=\"right\" />\r\n </div>\r\n <ToolbarDivider />\r\n <div className=\"lumir-toolbar-group\">\r\n <ListButton editor={editor} type=\"bullet\" />\r\n <ListButton editor={editor} type=\"numbered\" />\r\n </div>\r\n </div>\r\n <div className=\"lumir-toolbar-row\">\r\n <div className=\"lumir-toolbar-group\">\r\n <BlockTypeSelect editor={editor} />\r\n </div>\r\n <ToolbarDivider />\r\n <div className=\"lumir-toolbar-group\">\r\n <FontSizeButton editor={editor} />\r\n <ColorButton editor={editor} type=\"text\" />\r\n <ColorButton editor={editor} type=\"background\" />\r\n </div>\r\n <ToolbarDivider />\r\n <div className=\"lumir-toolbar-group\">\r\n <ImageButton editor={editor} onImageUpload={onImageUpload} />\r\n <LinkButton editor={editor} />\r\n <TableButton editor={editor} />\r\n <HTMLImportButton editor={editor} />\r\n </div>\r\n </div>\r\n </>\r\n );\r\n\r\n return (\r\n <div\r\n ref={wrapperRef}\r\n className={cn(\r\n \"lumir-floating-toolbar-wrapper\",\r\n isMinimizable && \"is-minimizable\",\r\n className\r\n )}\r\n data-position={position}\r\n >\r\n <div\r\n className={cn(\r\n \"lumir-floating-toolbar\",\r\n isCompact && \"is-compact\",\r\n isMinimizable && \"is-minimizable\",\r\n isMinimized && \"is-minimized\"\r\n )}\r\n >\r\n {isMinimizable ? (\r\n <MinimizedLayout />\r\n ) : isCompact ? (\r\n <TwoRowLayout />\r\n ) : (\r\n <SingleRowLayout />\r\n )}\r\n </div>\r\n </div>\r\n );\r\n};\r\n\r\n// 하위 호환성을 위한 default export\r\nexport default FloatingMenu;\r\n","\"use client\";\r\n\r\nimport React from \"react\";\r\n\r\n/**\r\n * FloatingMenu에서 사용하는 SVG 아이콘 컴포넌트들\r\n */\r\nexport const Icons = {\r\n undo: (\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" width=\"20\" height=\"20\">\r\n <path d=\"M12.5 8c-2.65 0-5.05.99-6.9 2.6L2 7v9h9l-3.62-3.62c1.39-1.16 3.16-1.88 5.12-1.88 3.54 0 6.55 2.31 7.6 5.5l2.37-.78C21.08 11.03 17.15 8 12.5 8z\" />\r\n </svg>\r\n ),\r\n redo: (\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" width=\"20\" height=\"20\">\r\n <path d=\"M18.4 10.6C16.55 8.99 14.15 8 11.5 8c-4.65 0-8.58 3.03-9.96 7.22L3.9 16c1.05-3.19 4.05-5.5 7.6-5.5 1.95 0 3.73.72 5.12 1.88L13 16h9V7l-3.6 3.6z\" />\r\n </svg>\r\n ),\r\n bold: (\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" width=\"20\" height=\"20\">\r\n <path d=\"M15.6 10.79c.97-.67 1.65-1.77 1.65-2.79 0-2.26-1.75-4-4-4H7v14h7.04c2.09 0 3.71-1.7 3.71-3.79 0-1.52-.86-2.82-2.15-3.42zM10 6.5h3c.83 0 1.5.67 1.5 1.5s-.67 1.5-1.5 1.5h-3v-3zm3.5 9H10v-3h3.5c.83 0 1.5.67 1.5 1.5s-.67 1.5-1.5 1.5z\" />\r\n </svg>\r\n ),\r\n italic: (\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" width=\"20\" height=\"20\">\r\n <path d=\"M10 4v3h2.21l-3.42 8H6v3h8v-3h-2.21l3.42-8H18V4z\" />\r\n </svg>\r\n ),\r\n underline: (\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" width=\"20\" height=\"20\">\r\n <path d=\"M12 17c3.31 0 6-2.69 6-6V3h-2.5v8c0 1.93-1.57 3.5-3.5 3.5S8.5 12.93 8.5 11V3H6v8c0 3.31 2.69 6 6 6zm-7 2v2h14v-2H5z\" />\r\n </svg>\r\n ),\r\n strikethrough: (\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" width=\"20\" height=\"20\">\r\n <path d=\"M10 19h4v-3h-4v3zM5 4v3h5v3h4V7h5V4H5zM3 14h18v-2H3v2z\" />\r\n </svg>\r\n ),\r\n alignLeft: (\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" width=\"20\" height=\"20\">\r\n <path d=\"M15 15H3v2h12v-2zm0-8H3v2h12V7zM3 13h18v-2H3v2zm0 8h18v-2H3v2zM3 3v2h18V3H3z\" />\r\n </svg>\r\n ),\r\n alignCenter: (\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" width=\"20\" height=\"20\">\r\n <path d=\"M7 15v2h10v-2H7zm-4 6h18v-2H3v2zm0-8h18v-2H3v2zm4-6v2h10V7H7zM3 3v2h18V3H3z\" />\r\n </svg>\r\n ),\r\n alignRight: (\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" width=\"20\" height=\"20\">\r\n <path d=\"M3 21h18v-2H3v2zm6-4h12v-2H9v2zm-6-4h18v-2H3v2zm6-4h12V7H9v2zM3 3v2h18V3H3z\" />\r\n </svg>\r\n ),\r\n bulletList: (\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" width=\"20\" height=\"20\">\r\n <path d=\"M4 10.5c-.83 0-1.5.67-1.5 1.5s.67 1.5 1.5 1.5 1.5-.67 1.5-1.5-.67-1.5-1.5-1.5zm0-6c-.83 0-1.5.67-1.5 1.5S3.17 7.5 4 7.5 5.5 6.83 5.5 6 4.83 4.5 4 4.5zm0 12c-.83 0-1.5.68-1.5 1.5s.68 1.5 1.5 1.5 1.5-.68 1.5-1.5-.67-1.5-1.5-1.5zM7 19h14v-2H7v2zm0-6h14v-2H7v2zm0-8v2h14V5H7z\" />\r\n </svg>\r\n ),\r\n numberedList: (\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" width=\"20\" height=\"20\">\r\n <path d=\"M2 17h2v.5H3v1h1v.5H2v1h3v-4H2v1zm1-9h1V4H2v1h1v3zm-1 3h1.8L2 13.1v.9h3v-1H3.2L5 10.9V10H2v1zm5-6v2h14V5H7zm0 14h14v-2H7v2zm0-6h14v-2H7v2z\" />\r\n </svg>\r\n ),\r\n image: (\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" width=\"20\" height=\"20\">\r\n <path d=\"M21 19V5c0-1.1-.9-2-2-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2zM8.5 13.5l2.5 3.01L14.5 12l4.5 6H5l3.5-4.5z\" />\r\n </svg>\r\n ),\r\n expandMore: (\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" width=\"18\" height=\"18\">\r\n <path d=\"M16.59 8.59L12 13.17 7.41 8.59 6 10l6 6 6-6z\" />\r\n </svg>\r\n ),\r\n textColor: (\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" width=\"20\" height=\"20\">\r\n <path d=\"M11 3L5.5 17h2.25l1.12-3h6.25l1.12 3h2.25L13 3h-2zm-1.38 9L12 5.67 14.38 12H9.62z\" />\r\n </svg>\r\n ),\r\n bgColor: (\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" width=\"20\" height=\"20\">\r\n <path d=\"M16.56 8.94L7.62 0 6.21 1.41l2.38 2.38-5.15 5.15c-.59.59-.59 1.54 0 2.12l5.5 5.5c.29.29.68.44 1.06.44s.77-.15 1.06-.44l5.5-5.5c.59-.58.59-1.53 0-2.12zM5.21 10L10 5.21 14.79 10H5.21zM19 11.5s-2 2.17-2 3.5c0 1.1.9 2 2 2s2-.9 2-2c0-1.33-2-3.5-2-3.5z\" />\r\n <path fillOpacity=\".36\" d=\"M0 20h24v4H0z\" />\r\n </svg>\r\n ),\r\n link: (\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" width=\"20\" height=\"20\">\r\n <path d=\"M3.9 12c0-1.71 1.39-3.1 3.1-3.1h4V7H7c-2.76 0-5 2.24-5 5s2.24 5 5 5h4v-1.9H7c-1.71 0-3.1-1.39-3.1-3.1zM8 13h8v-2H8v2zm9-6h-4v1.9h4c1.71 0 3.1 1.39 3.1 3.1s-1.39 3.1-3.1 3.1h-4V17h4c2.76 0 5-2.24 5-5s-2.24-5-5-5z\" />\r\n </svg>\r\n ),\r\n chevronRight: (\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" width=\"20\" height=\"20\">\r\n <path d=\"M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z\" />\r\n </svg>\r\n ),\r\n chevronLeft: (\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" width=\"20\" height=\"20\">\r\n <path d=\"M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z\" />\r\n </svg>\r\n ),\r\n table: (\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" width=\"20\" height=\"20\">\r\n <path d=\"M20 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h15c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zM10 17H5v-2h5v2zm0-4H5v-2h5v2zm0-4H5V7h5v2zm9 8h-7v-2h7v2zm0-4h-7v-2h7v2zm0-4h-7V7h7v2z\" />\r\n </svg>\r\n ),\r\n htmlFile: (\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" width=\"20\" height=\"20\">\r\n <path d=\"M14 2H6c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V8l-6-6zm-1 2v5h5l-5-5zm-4 14H7v-1h2v1zm0-2H7v-1h2v1zm-2-2h2v1H7v-1zm4 4h-2v-1h2v1zm0-2h-2v-1h2v1zm0-2h-2v-1h2v1zm6 4h-4v-1h4v1zm0-2h-4v-1h4v1zm0-2h-4v-1h4v1z\" />\r\n </svg>\r\n ),\r\n};\r\n\r\n/**\r\n * 블록 타입 아이콘들\r\n */\r\nexport const BlockTypeIcons: Record<string, React.ReactNode> = {\r\n paragraph: (\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" width=\"20\" height=\"20\">\r\n <path d=\"M5 5h14v2H5zM5 11h14v2H5zM5 17h10v2H5z\" />\r\n </svg>\r\n ),\r\n h1: <span className=\"lumir-block-icon-text\">H1</span>,\r\n h2: <span className=\"lumir-block-icon-text\">H2</span>,\r\n h3: <span className=\"lumir-block-icon-text\">H3</span>,\r\n h4: <span className=\"lumir-block-icon-text\">H4</span>,\r\n h5: <span className=\"lumir-block-icon-text\">H5</span>,\r\n h6: <span className=\"lumir-block-icon-text\">H6</span>,\r\n toggleH1: (\r\n <span className=\"lumir-block-icon-toggle\">\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" width=\"8\" height=\"8\">\r\n <path d=\"M8 5v14l11-7z\" />\r\n </svg>\r\n <span>H1</span>\r\n </span>\r\n ),\r\n toggleH2: (\r\n <span className=\"lumir-block-icon-toggle\">\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" width=\"8\" height=\"8\">\r\n <path d=\"M8 5v14l11-7z\" />\r\n </svg>\r\n <span>H2</span>\r\n </span>\r\n ),\r\n toggleH3: (\r\n <span className=\"lumir-block-icon-toggle\">\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" width=\"8\" height=\"8\">\r\n <path d=\"M8 5v14l11-7z\" />\r\n </svg>\r\n <span>H3</span>\r\n </span>\r\n ),\r\n quote: (\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" width=\"20\" height=\"20\">\r\n <path d=\"M6 17h3l2-4V7H5v6h3zm8 0h3l2-4V7h-6v6h3z\" />\r\n </svg>\r\n ),\r\n codeBlock: (\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" width=\"20\" height=\"20\">\r\n <path d=\"M9.4 16.6L4.8 12l4.6-4.6L8 6l-6 6 6 6 1.4-1.4zm5.2 0l4.6-4.6-4.6-4.6L16 6l6 6-6 6-1.4-1.4z\" />\r\n </svg>\r\n ),\r\n toggleList: (\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" width=\"20\" height=\"20\">\r\n <path d=\"M10 6h10v2H10zM10 11h10v2H10zM10 16h10v2H10z\" />\r\n <path\r\n d=\"M4 8l4 4-4 4\"\r\n stroke=\"currentColor\"\r\n strokeWidth=\"2\"\r\n fill=\"none\"\r\n strokeLinecap=\"round\"\r\n strokeLinejoin=\"round\"\r\n />\r\n </svg>\r\n ),\r\n bulletList: (\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" width=\"20\" height=\"20\">\r\n <circle cx=\"4\" cy=\"6\" r=\"1.5\" />\r\n <circle cx=\"4\" cy=\"12\" r=\"1.5\" />\r\n <circle cx=\"4\" cy=\"18\" r=\"1.5\" />\r\n <path d=\"M8 5h12v2H8zM8 11h12v2H8zM8 17h12v2H8z\" />\r\n </svg>\r\n ),\r\n numberedList: (\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" width=\"20\" height=\"20\">\r\n <path d=\"M2 17h2v.5H3v1h1v.5H2v1h3v-4H2v1zm1-9h1V4H2v1h1v3zm-1 3h1.8L2 13.1v.9h3v-1H3.2L5 10.9V10H2v1zm5-6v2h14V5H7zm0 14h14v-2H7v2zm0-6h14v-2H7v2z\" />\r\n </svg>\r\n ),\r\n checkList: (\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" width=\"20\" height=\"20\">\r\n <rect\r\n x=\"3\"\r\n y=\"4\"\r\n width=\"6\"\r\n height=\"6\"\r\n rx=\"1\"\r\n fill=\"none\"\r\n stroke=\"currentColor\"\r\n strokeWidth=\"1.5\"\r\n />\r\n <path\r\n d=\"M4.5 7l1.5 1.5 3-3\"\r\n stroke=\"currentColor\"\r\n strokeWidth=\"1.5\"\r\n fill=\"none\"\r\n strokeLinecap=\"round\"\r\n strokeLinejoin=\"round\"\r\n />\r\n <path d=\"M12 6h8v2h-8z\" />\r\n <rect\r\n x=\"3\"\r\n y=\"14\"\r\n width=\"6\"\r\n height=\"6\"\r\n rx=\"1\"\r\n fill=\"none\"\r\n stroke=\"currentColor\"\r\n strokeWidth=\"1.5\"\r\n />\r\n <path d=\"M12 16h8v2h-8z\" />\r\n </svg>\r\n ),\r\n};\r\n","\"use client\";\r\n\r\nimport React from \"react\";\r\n\r\n/**\r\n * 툴바 구분선 컴포넌트\r\n */\r\nexport const ToolbarDivider: React.FC = () => (\r\n <div className=\"lumir-toolbar-divider\" />\r\n);\r\n","\"use client\";\r\n\r\nimport React, { useCallback } from \"react\";\r\nimport type { EditorType } from \"../../../types\";\r\nimport { Icons } from \"../Icons\";\r\n\r\ninterface UndoRedoButtonsProps {\r\n editor: EditorType | any;\r\n}\r\n\r\n/**\r\n * 실행 취소 / 다시 실행 버튼 컴포넌트\r\n */\r\nexport const UndoRedoButtons: React.FC<UndoRedoButtonsProps> = ({ editor }) => {\r\n const handleUndo = useCallback(() => {\r\n try {\r\n editor?.undo?.();\r\n } catch (err) {\r\n console.error(\"Undo failed:\", err);\r\n }\r\n }, [editor]);\r\n\r\n const handleRedo = useCallback(() => {\r\n try {\r\n editor?.redo?.();\r\n } catch (err) {\r\n console.error(\"Redo failed:\", err);\r\n }\r\n }, [editor]);\r\n\r\n // 버튼 클릭 시 에디터 포커스/선택 영역 유지\r\n const handleMouseDown = useCallback((e: React.MouseEvent) => {\r\n e.preventDefault();\r\n }, []);\r\n\r\n return (\r\n <div className=\"lumir-toolbar-group\">\r\n <button\r\n className=\"lumir-toolbar-btn\"\r\n onClick={handleUndo}\r\n onMouseDown={handleMouseDown}\r\n title=\"실행 취소\"\r\n type=\"button\"\r\n >\r\n {Icons.undo}\r\n </button>\r\n <button\r\n className=\"lumir-toolbar-btn\"\r\n onClick={handleRedo}\r\n onMouseDown={handleMouseDown}\r\n title=\"다시 실행\"\r\n type=\"button\"\r\n >\r\n {Icons.redo}\r\n </button>\r\n </div>\r\n );\r\n};\r\n","\"use client\";\r\n\r\nimport React, { useCallback } from \"react\";\r\nimport type { EditorType } from \"../../../types\";\r\nimport { Icons } from \"../Icons\";\r\nimport { cn } from \"../../../utils/cn\";\r\n\r\ntype TextStyle = \"bold\" | \"italic\" | \"underline\" | \"strike\";\r\n\r\ninterface TextStyleButtonProps {\r\n editor: EditorType | any;\r\n style: TextStyle;\r\n}\r\n\r\nconst iconMap: Record<TextStyle, React.ReactNode> = {\r\n bold: Icons.bold,\r\n italic: Icons.italic,\r\n underline: Icons.underline,\r\n strike: Icons.strikethrough,\r\n};\r\n\r\nconst titleMap: Record<TextStyle, string> = {\r\n bold: \"굵게\",\r\n italic: \"기울임\",\r\n underline: \"밑줄\",\r\n strike: \"취소선\",\r\n};\r\n\r\n/**\r\n * 텍스트 스타일 버튼 (굵게, 기울임, 밑줄, 취소선)\r\n */\r\nexport const TextStyleButton: React.FC<TextStyleButtonProps> = ({\r\n editor,\r\n style,\r\n}) => {\r\n // 현재 스타일 상태를 직접 계산\r\n const getIsActive = (): boolean => {\r\n try {\r\n const activeStyles = editor?.getActiveStyles?.() || {};\r\n return activeStyles[style] === true;\r\n } catch {\r\n return false;\r\n }\r\n };\r\n\r\n const isActive = getIsActive();\r\n\r\n const handleClick = useCallback(() => {\r\n try {\r\n editor?.toggleStyles?.({ [style]: true });\r\n } catch (err) {\r\n console.error(`Toggle ${style} failed:`, err);\r\n }\r\n }, [editor, style]);\r\n\r\n // 버튼 클릭 시 에디터 포커스/선택 영역 유지\r\n const handleMouseDown = useCallback((e: React.MouseEvent) => {\r\n e.preventDefault();\r\n }, []);\r\n\r\n return (\r\n <button\r\n className={cn(\"lumir-toolbar-btn\", isActive && \"is-active\")}\r\n onClick={handleClick}\r\n onMouseDown={handleMouseDown}\r\n title={titleMap[style]}\r\n type=\"button\"\r\n >\r\n {iconMap[style]}\r\n </button>\r\n );\r\n};\r\n","\"use client\";\r\n\r\nimport React, { useCallback } from \"react\";\r\nimport type { EditorType } from \"../../../types\";\r\nimport { Icons } from \"../Icons\";\r\nimport { cn } from \"../../../utils/cn\";\r\nimport {\r\n getFirstSelectedCellAttr,\r\n isInTableCell,\r\n setSelectedCellsAttr,\r\n} from \"../../../utils/prosemirror-table-utils\";\r\n\r\ntype Alignment = \"left\" | \"center\" | \"right\";\r\n\r\ninterface AlignButtonProps {\r\n editor: EditorType | any;\r\n alignment: Alignment;\r\n}\r\n\r\nconst iconMap: Record<Alignment, React.ReactNode> = {\r\n left: Icons.alignLeft,\r\n center: Icons.alignCenter,\r\n right: Icons.alignRight,\r\n};\r\n\r\nconst titleMap: Record<Alignment, string> = {\r\n left: \"왼쪽 정렬\",\r\n center: \"가운데 정렬\",\r\n right: \"오른쪽 정렬\",\r\n};\r\n\r\n/**\r\n * 텍스트 정렬 버튼 (왼쪽, 가운데, 오른쪽)\r\n */\r\nexport const AlignButton: React.FC<AlignButtonProps> = ({\r\n editor,\r\n alignment,\r\n}) => {\r\n // 현재 정렬 상태를 직접 계산 (표 셀이면 셀 attr, 아니면 블록 prop)\r\n const getCurrentAlignment = (): string => {\r\n try {\r\n if (isInTableCell(editor)) {\r\n return (\r\n (getFirstSelectedCellAttr(editor, \"textAlignment\") as string) ||\r\n \"left\"\r\n );\r\n }\r\n const block = editor?.getTextCursorPosition()?.block;\r\n return block?.props?.textAlignment || \"left\";\r\n } catch {\r\n return \"left\";\r\n }\r\n };\r\n\r\n const isActive = getCurrentAlignment() === alignment;\r\n\r\n const handleClick = useCallback(() => {\r\n try {\r\n // 표 셀 안이면 셀 단위 정렬(선택 유지), 아니면 블록 정렬.\r\n if (setSelectedCellsAttr(editor, \"textAlignment\", alignment)) {\r\n return;\r\n }\r\n const block = editor?.getTextCursorPosition()?.block;\r\n if (block && editor?.updateBlock) {\r\n editor.updateBlock(block, { props: { textAlignment: alignment } });\r\n }\r\n } catch (err) {\r\n console.error(`Set alignment ${alignment} failed:`, err);\r\n }\r\n }, [editor, alignment]);\r\n\r\n // 버튼 클릭 시 에디터 포커스/선택 영역 유지\r\n const handleMouseDown = useCallback((e: React.MouseEvent) => {\r\n e.preventDefault();\r\n }, []);\r\n\r\n return (\r\n <button\r\n className={cn(\"lumir-toolbar-btn\", isActive && \"is-active\")}\r\n onClick={handleClick}\r\n onMouseDown={handleMouseDown}\r\n title={titleMap[alignment]}\r\n type=\"button\"\r\n >\r\n {iconMap[alignment]}\r\n </button>\r\n );\r\n};\r\n","/**\r\n * ProseMirror selection에서 현재 선택된 테이블 셀의 위치(pos)를 반환한다.\r\n * CellSelection이면 선택된 모든 셀, 일반 selection이면 커서가 있는 셀.\r\n * prosemirror-tables 직접 임포트 없이 duck-typing으로 CellSelection을 감지한다.\r\n */\r\nexport function getSelectedCellPositions(editor: any): number[] {\r\n const tiptap = editor._tiptapEditor;\r\n if (!tiptap) return [];\r\n\r\n const { state } = tiptap;\r\n const { selection } = state;\r\n\r\n if (typeof selection.forEachCell === \"function\") {\r\n const positions: number[] = [];\r\n selection.forEachCell((_node: any, pos: number) => {\r\n positions.push(pos);\r\n });\r\n return positions;\r\n }\r\n\r\n const $pos = selection.$from;\r\n for (let depth = $pos.depth; depth > 0; depth--) {\r\n const node = $pos.node(depth);\r\n if (\r\n node.type.name === \"tableCell\" ||\r\n node.type.name === \"tableHeader\"\r\n ) {\r\n return [$pos.before(depth)];\r\n }\r\n }\r\n\r\n return [];\r\n}\r\n\r\n/**\r\n * 주어진 셀 위치들에 노드 attr(예: backgroundColor/textColor/textAlignment)를\r\n * setNodeMarkup으로 일괄 설정한다. setNodeMarkup은 문서 구조를 바꾸지 않으므로\r\n * **CellSelection이 유지**된다(updateBlock은 내용 전체를 교체해 선택이 풀림).\r\n *\r\n * backgroundColor/textColor는 BlockNote의 글로벌 attribute(tableCell/tableHeader),\r\n * textAlignment도 셀 노드 attr이므로 setNodeMarkup에 유효하다.\r\n *\r\n * @returns 적용된 셀이 하나라도 있으면 true(= 표 셀 컨텍스트), 아니면 false.\r\n */\r\nexport function setCellAttrAtPositions(\r\n editor: any,\r\n positions: number[],\r\n attr: string,\r\n value: unknown,\r\n): boolean {\r\n const tiptap = editor?._tiptapEditor;\r\n if (!tiptap || positions.length === 0) return false;\r\n\r\n let tr = tiptap.state.tr;\r\n let changed = false;\r\n for (const pos of positions) {\r\n const node = tr.doc.nodeAt(pos);\r\n if (\r\n node &&\r\n (node.type.name === \"tableCell\" || node.type.name === \"tableHeader\")\r\n ) {\r\n // setNodeMarkup은 attr만 바꾸고 위치/크기는 그대로 → 선택 매핑이 유지됨.\r\n tr = tr.setNodeMarkup(pos, undefined, { ...node.attrs, [attr]: value });\r\n changed = true;\r\n }\r\n }\r\n if (changed) {\r\n tiptap.view?.dispatch(tr);\r\n }\r\n return changed;\r\n}\r\n\r\n/**\r\n * 현재 selection의 셀(들)에 attr를 설정한다. 표 셀 컨텍스트가 아니면 false 반환\r\n * → 호출부가 블록/인라인 폴백을 적용할 수 있다.\r\n */\r\nexport function setSelectedCellsAttr(\r\n editor: any,\r\n attr: string,\r\n value: unknown,\r\n): boolean {\r\n return setCellAttrAtPositions(\r\n editor,\r\n getSelectedCellPositions(editor),\r\n attr,\r\n value,\r\n );\r\n}\r\n\r\n/** 현재 selection이 표 셀 안인지(단일/다중 무관). */\r\nexport function isInTableCell(editor: any): boolean {\r\n return getSelectedCellPositions(editor).length > 0;\r\n}\r\n\r\n/** 첫 선택 셀의 노드 attr 값을 읽는다(활성 상태 표시용). 없으면 undefined. */\r\nexport function getFirstSelectedCellAttr(\r\n editor: any,\r\n attr: string,\r\n): unknown {\r\n const tiptap = editor?._tiptapEditor;\r\n const positions = getSelectedCellPositions(editor);\r\n if (!tiptap || positions.length === 0) return undefined;\r\n const node = tiptap.state.doc.nodeAt(positions[0]);\r\n return node?.attrs?.[attr];\r\n}\r\n\r\n/**\r\n * blockId로 식별되는 table 블록의 ProseMirror 노드 pos를 찾는다.\r\n * blockContainer(id 일치) > firstChild(table)의 pos. 없으면 -1.\r\n */\r\nexport function findTableNodePos(tiptap: any, blockId: string): number {\r\n let tablePos = -1;\r\n tiptap.state.doc.descendants((node: any, pos: number) => {\r\n if (tablePos !== -1) return false;\r\n if (\r\n node.type.name === \"blockContainer\" &&\r\n node.attrs?.id === blockId &&\r\n node.firstChild?.type.name === \"table\"\r\n ) {\r\n // blockContainer content 시작(= table 노드 pos)\r\n tablePos = pos + 1;\r\n return false;\r\n }\r\n return undefined;\r\n });\r\n return tablePos;\r\n}\r\n\r\n/**\r\n * table 블록의 노드 attr `tableAlignment`를 설정한다(setNodeMarkup → 구조/선택 유지).\r\n * @returns 적용 성공 여부.\r\n */\r\nexport function setTableAlignment(\r\n editor: any,\r\n blockId: string,\r\n alignment: \"left\" | \"center\" | \"right\",\r\n): boolean {\r\n const tiptap = editor?._tiptapEditor;\r\n if (!tiptap || !blockId) return false;\r\n const tablePos = findTableNodePos(tiptap, blockId);\r\n if (tablePos < 0) return false;\r\n const node = tiptap.state.doc.nodeAt(tablePos);\r\n if (!node || node.type.name !== \"table\") return false;\r\n tiptap.view?.dispatch(\r\n tiptap.state.tr.setNodeMarkup(tablePos, undefined, {\r\n ...node.attrs,\r\n tableAlignment: alignment,\r\n }),\r\n );\r\n return true;\r\n}\r\n\r\n/** table 블록의 현재 정렬값을 읽는다(활성 상태 표시용). 기본 \"left\". */\r\nexport function getTableAlignment(editor: any, blockId: string): string {\r\n const tiptap = editor?._tiptapEditor;\r\n if (!tiptap || !blockId) return \"left\";\r\n const tablePos = findTableNodePos(tiptap, blockId);\r\n if (tablePos < 0) return \"left\";\r\n const node = tiptap.state.doc.nodeAt(tablePos);\r\n return (node?.attrs?.tableAlignment as string) || \"left\";\r\n}\r\n","\"use client\";\r\n\r\nimport React, { useCallback } from \"react\";\r\nimport type { EditorType } from \"../../../types\";\r\nimport { Icons } from \"../Icons\";\r\nimport { cn } from \"../../../utils/cn\";\r\n\r\ntype ListType = \"bullet\" | \"numbered\";\r\n\r\ninterface ListButtonProps {\r\n editor: EditorType | any;\r\n type: ListType;\r\n}\r\n\r\nconst iconMap: Record<ListType, React.ReactNode> = {\r\n bullet: Icons.bulletList,\r\n numbered: Icons.numberedList,\r\n};\r\n\r\nconst titleMap: Record<ListType, string> = {\r\n bullet: \"글머리 기호 목록\",\r\n numbered: \"번호 목록\",\r\n};\r\n\r\n/**\r\n * 리스트 버튼 (글머리 기호, 번호 목록)\r\n */\r\nexport const ListButton: React.FC<ListButtonProps> = ({ editor, type }) => {\r\n // 현재 리스트 상태를 직접 계산\r\n const getIsActive = (): boolean => {\r\n try {\r\n const block = editor?.getTextCursorPosition()?.block;\r\n const blockType =\r\n type === \"bullet\" ? \"bulletListItem\" : \"numberedListItem\";\r\n return block?.type === blockType;\r\n } catch {\r\n return false;\r\n }\r\n };\r\n\r\n const isActive = getIsActive();\r\n\r\n const handleClick = useCallback(() => {\r\n try {\r\n const block = editor?.getTextCursorPosition()?.block;\r\n if (block && editor?.updateBlock) {\r\n const targetType =\r\n type === \"bullet\" ? \"bulletListItem\" : \"numberedListItem\";\r\n const newType = block.type === targetType ? \"paragraph\" : targetType;\r\n editor.updateBlock(block, { type: newType as any });\r\n }\r\n } catch (err) {\r\n console.error(`List toggle failed:`, err);\r\n }\r\n }, [editor, type]);\r\n\r\n // 버튼 클릭 시 에디터 포커스/선택 영역 유지\r\n const handleMouseDown = useCallback((e: React.MouseEvent) => {\r\n e.preventDefault();\r\n }, []);\r\n\r\n return (\r\n <button\r\n className={cn(\"lumir-toolbar-btn\", isActive && \"is-active\")}\r\n onClick={handleClick}\r\n onMouseDown={handleMouseDown}\r\n title={titleMap[type]}\r\n type=\"button\"\r\n >\r\n {iconMap[type]}\r\n </button>\r\n );\r\n};\r\n","\"use client\";\r\n\r\nimport React, { useCallback } from \"react\";\r\nimport type { EditorType } from \"../../../types\";\r\nimport { Icons } from \"../Icons\";\r\n\r\ninterface ImageButtonProps {\r\n editor: EditorType | any;\r\n onImageUpload?: () => void;\r\n}\r\n\r\n/**\r\n * 이미지 업로드 버튼\r\n */\r\nexport const ImageButton: React.FC<ImageButtonProps> = ({\r\n editor,\r\n onImageUpload,\r\n}) => {\r\n const handleClick = useCallback(() => {\r\n if (onImageUpload) {\r\n onImageUpload();\r\n } else {\r\n const input = document.createElement(\"input\");\r\n input.type = \"file\";\r\n input.accept = \"image/*\";\r\n input.onchange = async (e) => {\r\n const file = (e.target as HTMLInputElement).files?.[0];\r\n if (file && editor?.uploadFile) {\r\n try {\r\n const url = await editor.uploadFile(file);\r\n editor.insertBlocks(\r\n [{ type: \"image\", props: { url: url as string } }] as any,\r\n editor.getTextCursorPosition().block,\r\n \"after\"\r\n );\r\n } catch (err) {\r\n console.error(\"Image upload failed:\", err);\r\n }\r\n }\r\n };\r\n input.click();\r\n }\r\n }, [editor, onImageUpload]);\r\n\r\n // 버튼 클릭 시 에디터 포커스/선택 영역 유지\r\n const handleMouseDown = useCallback((e: React.MouseEvent) => {\r\n e.preventDefault();\r\n }, []);\r\n\r\n return (\r\n <button\r\n className=\"lumir-toolbar-btn\"\r\n onClick={handleClick}\r\n onMouseDown={handleMouseDown}\r\n title=\"이미지 삽입\"\r\n type=\"button\"\r\n >\r\n {Icons.image}\r\n </button>\r\n );\r\n};\r\n","\"use client\";\r\n\r\nimport React, { useState, useEffect, useRef, useCallback } from \"react\";\r\nimport type { EditorType } from \"../../../types\";\r\nimport { Icons } from \"../Icons\";\r\nimport { cn } from \"../../../utils/cn\";\r\nimport {\r\n TEXT_COLORS,\r\n BACKGROUND_COLORS,\r\n getHexFromColorValue,\r\n} from \"../../../constants/colors\";\r\nimport {\r\n getFirstSelectedCellAttr,\r\n isInTableCell,\r\n setSelectedCellsAttr,\r\n} from \"../../../utils/prosemirror-table-utils\";\r\n\r\ntype ColorType = \"text\" | \"background\";\r\n\r\ninterface ColorButtonProps {\r\n editor: EditorType | any;\r\n type: ColorType;\r\n}\r\n\r\n/**\r\n * 색상 선택 버튼 (텍스트/배경 색상)\r\n */\r\nexport const ColorButton: React.FC<ColorButtonProps> = ({ editor, type }) => {\r\n const [isOpen, setIsOpen] = useState(false);\r\n const [currentColor, setCurrentColor] = useState(\"default\");\r\n const dropdownRef = useRef<HTMLDivElement>(null);\r\n\r\n const colors = type === \"text\" ? TEXT_COLORS : BACKGROUND_COLORS;\r\n\r\n const getCurrentColor = useCallback((): string => {\r\n try {\r\n // 표 셀 안이면 셀의 색 attr을 읽는다(인라인 활성 스타일이 아님).\r\n if (isInTableCell(editor)) {\r\n const attr = type === \"text\" ? \"textColor\" : \"backgroundColor\";\r\n return (getFirstSelectedCellAttr(editor, attr) as string) || \"default\";\r\n }\r\n const activeStyles = editor?.getActiveStyles?.() || {};\r\n if (type === \"text\" && activeStyles.textColor) {\r\n return activeStyles.textColor;\r\n } else if (type === \"background\" && activeStyles.backgroundColor) {\r\n return activeStyles.backgroundColor;\r\n }\r\n } catch {\r\n // ignore\r\n }\r\n return \"default\";\r\n }, [editor, type]);\r\n\r\n // 드롭다운 열릴 때 현재 색상 업데이트\r\n useEffect(() => {\r\n if (isOpen) {\r\n const color = getCurrentColor();\r\n setCurrentColor(color);\r\n }\r\n }, [isOpen, getCurrentColor]);\r\n\r\n // 외부 클릭 감지\r\n useEffect(() => {\r\n const handleClickOutside = (e: MouseEvent) => {\r\n if (\r\n dropdownRef.current &&\r\n !dropdownRef.current.contains(e.target as Node)\r\n ) {\r\n setIsOpen(false);\r\n }\r\n };\r\n document.addEventListener(\"mousedown\", handleClickOutside);\r\n return () => document.removeEventListener(\"mousedown\", handleClickOutside);\r\n }, []);\r\n\r\n const handleColorSelect = useCallback(\r\n (color: string) => {\r\n try {\r\n if (!editor) return;\r\n\r\n const attr = type === \"text\" ? \"textColor\" : \"backgroundColor\";\r\n // 표 셀 안이면 셀 단위로 적용(선택 유지), 아니면 인라인 스타일.\r\n if (!setSelectedCellsAttr(editor, attr, color)) {\r\n (editor as any).toggleStyles(\r\n type === \"text\"\r\n ? { textColor: color }\r\n : { backgroundColor: color },\r\n );\r\n }\r\n setCurrentColor(color);\r\n setIsOpen(false);\r\n setTimeout(() => editor.focus?.());\r\n } catch (err) {\r\n console.error(`Color apply failed:`, err);\r\n }\r\n },\r\n [editor, type]\r\n );\r\n\r\n // 버튼 클릭 시 에디터 포커스/선택 영역 유지\r\n const handleMouseDown = useCallback((e: React.MouseEvent) => {\r\n e.preventDefault();\r\n }, []);\r\n\r\n return (\r\n <div className=\"lumir-dropdown-wrapper\" ref={dropdownRef}>\r\n <button\r\n className=\"lumir-toolbar-btn lumir-color-btn\"\r\n onClick={() => setIsOpen(!isOpen)}\r\n onMouseDown={handleMouseDown}\r\n title={type === \"text\" ? \"텍스트 색상\" : \"배경 색상\"}\r\n type=\"button\"\r\n >\r\n {type === \"text\" ? Icons.textColor : Icons.bgColor}\r\n <span\r\n className=\"lumir-color-indicator\"\r\n style={{\r\n backgroundColor: getHexFromColorValue(currentColor, type),\r\n }}\r\n />\r\n </button>\r\n {isOpen && (\r\n <div className=\"lumir-dropdown-menu lumir-color-menu\">\r\n <div className=\"lumir-color-grid\">\r\n {colors.map((color) => (\r\n <button\r\n key={color.value}\r\n className={cn(\r\n \"lumir-color-swatch\",\r\n currentColor === color.value && \"is-active\"\r\n )}\r\n onClick={() => handleColorSelect(color.value)}\r\n onMouseDown={handleMouseDown}\r\n title={color.name}\r\n style={{ backgroundColor: color.hex }}\r\n type=\"button\"\r\n />\r\n ))}\r\n </div>\r\n </div>\r\n )}\r\n </div>\r\n );\r\n};\r\n","/**\r\n * 색상 팔레트 상수\r\n * BlockNote 기본 색상 팔레트와 일치\r\n */\r\n\r\nexport interface ColorItem {\r\n name: string;\r\n value: string;\r\n hex: string;\r\n}\r\n\r\n/**\r\n * 텍스트 색상 팔레트\r\n */\r\nexport const TEXT_COLORS: ColorItem[] = [\r\n { name: \"기본\", value: \"default\", hex: \"#3f3f3f\" },\r\n { name: \"회색\", value: \"gray\", hex: \"#9b9a97\" },\r\n { name: \"갈색\", value: \"brown\", hex: \"#64473a\" },\r\n { name: \"빨간색\", value: \"red\", hex: \"#e03e3e\" },\r\n { name: \"주황색\", value: \"orange\", hex: \"#d9730d\" },\r\n { name: \"노란색\", value: \"yellow\", hex: \"#dfab01\" },\r\n { name: \"초록색\", value: \"green\", hex: \"#4d6461\" },\r\n { name: \"파란색\", value: \"blue\", hex: \"#0b6e99\" },\r\n { name: \"보라색\", value: \"purple\", hex: \"#6940a5\" },\r\n { name: \"분홍색\", value: \"pink\", hex: \"#ad1a72\" },\r\n];\r\n\r\n/**\r\n * 배경 색상 팔레트\r\n */\r\nexport const BACKGROUND_COLORS: ColorItem[] = [\r\n { name: \"기본\", value: \"default\", hex: \"transparent\" },\r\n { name: \"회색\", value: \"gray\", hex: \"#ebeced\" },\r\n { name: \"갈색\", value: \"brown\", hex: \"#e9e5e3\" },\r\n { name: \"빨간색\", value: \"red\", hex: \"#fbe4e4\" },\r\n { name: \"주황색\", value: \"orange\", hex: \"#f6e9d9\" },\r\n { name: \"노란색\", value: \"yellow\", hex: \"#fbf3db\" },\r\n { name: \"초록색\", value: \"green\", hex: \"#ddedea\" },\r\n { name: \"파란색\", value: \"blue\", hex: \"#ddebf1\" },\r\n { name: \"보라색\", value: \"purple\", hex: \"#eae4f2\" },\r\n { name: \"분홍색\", value: \"pink\", hex: \"#f4dfeb\" },\r\n];\r\n\r\n/**\r\n * 색상 값으로 hex 색상 코드 찾기\r\n */\r\nexport const getHexFromColorValue = (\r\n value: string,\r\n type: \"text\" | \"background\"\r\n): string => {\r\n const colors = type === \"text\" ? TEXT_COLORS : BACKGROUND_COLORS;\r\n const colorItem = colors.find((c) => c.value === value);\r\n return colorItem?.hex || (type === \"text\" ? \"#000000\" : \"transparent\");\r\n};\r\n","\"use client\";\r\n\r\nimport React, { useState, useEffect, useRef, useCallback } from \"react\";\r\nimport type { EditorType } from \"../../../types\";\r\nimport { Icons } from \"../Icons\";\r\nimport { cn } from \"../../../utils/cn\";\r\nimport {\r\n FONT_SIZE_MAX,\r\n FONT_SIZE_MIN,\r\n FONT_SIZE_PRESETS,\r\n parseFontSizePx,\r\n toFontSizeValue,\r\n} from \"../../../styles/FontSizeStyle\";\r\n\r\ninterface FontSizeButtonProps {\r\n editor: EditorType | any;\r\n}\r\n\r\nconst DEFAULT_LABEL = \"기본\";\r\n\r\n/** \"18px\" → \"18\" */\r\nconst toLabel = (size: string) => size.replace(/px$/, \"\");\r\n\r\n/**\r\n * 글자 크기 선택 드롭다운 (FloatingMenu용).\r\n *\r\n * 드롭다운 상단에 −/+ 스테퍼 + 직접 입력(1px 단위, 8~96px clamp)을 두고,\r\n * 그 아래 \"기본\" 리셋 + 프리셋 목록을 유지한다.\r\n *\r\n * ColorButton과 달리 테이블 셀 분기가 없다 — 셀 단위 font-size attr이 없으므로\r\n * 셀 내부에서도 항상 인라인 스타일(addStyles/removeStyles)로 적용한다.\r\n */\r\nexport const FontSizeButton: React.FC<FontSizeButtonProps> = ({ editor }) => {\r\n const [isOpen, setIsOpen] = useState(false);\r\n const dropdownRef = useRef<HTMLDivElement>(null);\r\n\r\n // 현재 인라인 글자 크기 (선택 변경 시 FloatingMenu가 tick으로 리렌더)\r\n const getCurrentSize = (): string => {\r\n try {\r\n return editor?.getActiveStyles?.()?.fontSize || \"\";\r\n } catch {\r\n return \"\";\r\n }\r\n };\r\n const currentSize = getCurrentSize();\r\n // 명시 크기가 없으면(\"기본\") 14px를 기준으로 증감/표시한다.\r\n const currentPx = parseFontSizePx(currentSize);\r\n\r\n // 직접 입력 필드의 로컬 상태 (실제 크기 변경 시 동기화)\r\n const [inputValue, setInputValue] = useState<string>(String(currentPx));\r\n useEffect(() => {\r\n setInputValue(String(currentPx));\r\n }, [currentPx]);\r\n\r\n // 외부 클릭 감지\r\n useEffect(() => {\r\n const handleClickOutside = (e: MouseEvent) => {\r\n if (\r\n dropdownRef.current &&\r\n !dropdownRef.current.contains(e.target as Node)\r\n ) {\r\n setIsOpen(false);\r\n }\r\n };\r\n document.addEventListener(\"mousedown\", handleClickOutside);\r\n return () => document.removeEventListener(\"mousedown\", handleClickOutside);\r\n }, []);\r\n\r\n const handleSizeSelect = useCallback(\r\n (size: string) => {\r\n try {\r\n if (!editor) return;\r\n if (size === \"\") {\r\n (editor as any).removeStyles?.({ fontSize: \"\" });\r\n } else {\r\n (editor as any).addStyles?.({ fontSize: size });\r\n }\r\n setIsOpen(false);\r\n setTimeout(() => editor.focus?.());\r\n } catch (err) {\r\n console.error(\"Font size apply failed:\", err);\r\n }\r\n },\r\n [editor]\r\n );\r\n\r\n // −/+ 스테퍼: 메뉴를 닫지 않고 1px씩 적용 (선택 영역 유지).\r\n const stepBy = useCallback(\r\n (delta: number) => {\r\n try {\r\n (editor as any)?.addStyles?.({\r\n fontSize: toFontSizeValue(currentPx + delta),\r\n });\r\n } catch (err) {\r\n console.error(\"Font size step failed:\", err);\r\n }\r\n },\r\n [editor, currentPx]\r\n );\r\n\r\n // 직접 입력 적용: 유효하면 clamp 후 적용, 무효/빈값이면 표시값 복원.\r\n const applyInput = useCallback(() => {\r\n const n = parseInt(inputValue, 10);\r\n if (Number.isFinite(n)) {\r\n try {\r\n (editor as any)?.addStyles?.({ fontSize: toFontSizeValue(n) });\r\n } catch (err) {\r\n console.error(\"Font size apply failed:\", err);\r\n }\r\n } else {\r\n setInputValue(String(currentPx));\r\n }\r\n }, [editor, inputValue, currentPx]);\r\n\r\n // 버튼 클릭 시 에디터 포커스/선택 영역 유지\r\n const handleMouseDown = useCallback((e: React.MouseEvent) => {\r\n e.preventDefault();\r\n }, []);\r\n\r\n return (\r\n <div className=\"lumir-dropdown-wrapper\" ref={dropdownRef}>\r\n <button\r\n className=\"lumir-dropdown-btn lumir-font-size-btn\"\r\n onClick={() => setIsOpen(!isOpen)}\r\n onMouseDown={handleMouseDown}\r\n title=\"글자 크기\"\r\n type=\"button\"\r\n >\r\n <span className=\"lumir-font-size-label\">\r\n {currentSize ? toLabel(currentSize) : DEFAULT_LABEL}\r\n </span>\r\n {Icons.expandMore}\r\n </button>\r\n {isOpen && (\r\n <div className=\"lumir-dropdown-menu lumir-font-size-menu\">\r\n {/* 1px 스테퍼 + 직접 입력 */}\r\n <div className=\"lumir-fs-stepper\">\r\n <button\r\n type=\"button\"\r\n className=\"lumir-fs-stepper-btn\"\r\n aria-label=\"글자 크기 1px 줄이기\"\r\n disabled={currentPx <= FONT_SIZE_MIN}\r\n onMouseDown={handleMouseDown}\r\n onClick={() => stepBy(-1)}\r\n >\r\n −\r\n </button>\r\n <input\r\n className=\"lumir-fs-stepper-input\"\r\n type=\"text\"\r\n inputMode=\"numeric\"\r\n aria-label=\"글자 크기 입력\"\r\n value={inputValue}\r\n onChange={(e) =>\r\n setInputValue(e.target.value.replace(/[^0-9]/g, \"\"))\r\n }\r\n onKeyDown={(e) => {\r\n e.stopPropagation();\r\n if (e.key === \"Enter\") {\r\n e.preventDefault();\r\n applyInput();\r\n } else if (e.key === \"ArrowUp\") {\r\n e.preventDefault();\r\n stepBy(1);\r\n } else if (e.key === \"ArrowDown\") {\r\n e.preventDefault();\r\n stepBy(-1);\r\n }\r\n }}\r\n onBlur={applyInput}\r\n />\r\n <button\r\n type=\"button\"\r\n className=\"lumir-fs-stepper-btn\"\r\n aria-label=\"글자 크기 1px 늘리기\"\r\n disabled={currentPx >= FONT_SIZE_MAX}\r\n onMouseDown={handleMouseDown}\r\n onClick={() => stepBy(1)}\r\n >\r\n +\r\n </button>\r\n </div>\r\n <button\r\n className={cn(\r\n \"lumir-dropdown-item\",\r\n currentSize === \"\" && \"is-active\"\r\n )}\r\n onClick={() => handleSizeSelect(\"\")}\r\n onMouseDown={handleMouseDown}\r\n type=\"button\"\r\n >\r\n {DEFAULT_LABEL}\r\n </button>\r\n {FONT_SIZE_PRESETS.map((size) => (\r\n <button\r\n key={size}\r\n className={cn(\r\n \"lumir-dropdown-item\",\r\n currentSize === size && \"is-active\"\r\n )}\r\n onClick={() => handleSizeSelect(size)}\r\n onMouseDown={handleMouseDown}\r\n type=\"button\"\r\n >\r\n {toLabel(size)}\r\n </button>\r\n ))}\r\n </div>\r\n )}\r\n </div>\r\n );\r\n};\r\n","\"use client\";\r\n\r\nimport React, { useState, useEffect, useRef, useCallback } from \"react\";\r\nimport type { EditorType } from \"../../../types\";\r\nimport { Icons } from \"../Icons\";\r\n\r\ninterface LinkButtonProps {\r\n editor: EditorType | any;\r\n}\r\n\r\n/**\r\n * 🔒 위험한 URL 프로토콜 검증\r\n * javascript:, data:, vbscript: 등 XSS 공격에 사용될 수 있는 프로토콜 차단\r\n */\r\nexport const isDangerousProtocol = (url: string): boolean => {\r\n const trimmedUrl = url.trim().toLowerCase();\r\n // 위험한 프로토콜 패턴\r\n const dangerousPatterns = [\r\n /^javascript:/i,\r\n /^data:/i,\r\n /^vbscript:/i,\r\n /^file:/i,\r\n ];\r\n return dangerousPatterns.some((pattern) => pattern.test(trimmedUrl));\r\n};\r\n\r\n/**\r\n * URL 프로토콜 자동 추가 유틸리티 (보안 강화)\r\n */\r\nexport const normalizeUrl = (url: string): string | null => {\r\n const trimmedUrl = url.trim();\r\n\r\n // 🔒 위험한 프로토콜 차단\r\n if (isDangerousProtocol(trimmedUrl)) {\r\n console.warn(\"Blocked dangerous URL protocol:\", trimmedUrl);\r\n return null;\r\n }\r\n\r\n // 이미 프로토콜이 있는 경우 그대로 반환\r\n if (/^https?:\\/\\//i.test(trimmedUrl)) {\r\n return trimmedUrl;\r\n }\r\n\r\n // mailto: 또는 tel: 링크인 경우 그대로 반환\r\n if (/^(mailto:|tel:)/i.test(trimmedUrl)) {\r\n return trimmedUrl;\r\n }\r\n\r\n // 프로토콜이 없는 경우 https:// 추가\r\n return `https://${trimmedUrl}`;\r\n};\r\n\r\n/**\r\n * 링크 삽입 버튼\r\n */\r\nexport const LinkButton: React.FC<LinkButtonProps> = ({ editor }) => {\r\n const [isOpen, setIsOpen] = useState(false);\r\n const [linkUrl, setLinkUrl] = useState(\"\");\r\n const [errorMsg, setErrorMsg] = useState<string | null>(null);\r\n const dropdownRef = useRef<HTMLDivElement>(null);\r\n const inputRef = useRef<HTMLInputElement>(null);\r\n const hasSelectionRef = useRef(false);\r\n\r\n // 외부 클릭 감지\r\n useEffect(() => {\r\n const handleClickOutside = (e: MouseEvent) => {\r\n if (\r\n dropdownRef.current &&\r\n !dropdownRef.current.contains(e.target as Node)\r\n ) {\r\n setIsOpen(false);\r\n setLinkUrl(\"\");\r\n setErrorMsg(null);\r\n }\r\n };\r\n document.addEventListener(\"mousedown\", handleClickOutside);\r\n return () => document.removeEventListener(\"mousedown\", handleClickOutside);\r\n }, []);\r\n\r\n // 드롭다운 열릴 때 선택 상태 저장 후 input에 포커스\r\n useEffect(() => {\r\n if (isOpen && inputRef.current) {\r\n try {\r\n const selectedText = editor?.getSelectedText?.() || \"\";\r\n hasSelectionRef.current = selectedText.length > 0;\r\n } catch {\r\n hasSelectionRef.current = false;\r\n }\r\n setTimeout(() => inputRef.current?.focus(), 0);\r\n }\r\n }, [isOpen, editor]);\r\n\r\n const handleSubmit = useCallback(\r\n (e?: React.FormEvent) => {\r\n e?.preventDefault();\r\n setErrorMsg(null);\r\n\r\n try {\r\n if (linkUrl.trim() && editor?.createLink) {\r\n const normalizedUrl = normalizeUrl(linkUrl);\r\n\r\n if (normalizedUrl === null) {\r\n setErrorMsg(\"허용되지 않는 URL 형식입니다.\");\r\n return;\r\n }\r\n\r\n editor.focus();\r\n\r\n if (hasSelectionRef.current) {\r\n editor.createLink(normalizedUrl);\r\n } else {\r\n editor.createLink(normalizedUrl, normalizedUrl);\r\n }\r\n\r\n setIsOpen(false);\r\n setLinkUrl(\"\");\r\n }\r\n } catch (err) {\r\n console.error(\"Create link failed:\", err);\r\n setErrorMsg(\"링크 생성에 실패했습니다.\");\r\n }\r\n },\r\n [editor, linkUrl]\r\n );\r\n\r\n const handleCancel = useCallback(() => {\r\n setIsOpen(false);\r\n setLinkUrl(\"\");\r\n setErrorMsg(null);\r\n }, []);\r\n\r\n // 버튼 클릭 시 에디터 포커스/선택 영역 유지\r\n const handleMouseDown = useCallback((e: React.MouseEvent) => {\r\n e.preventDefault();\r\n }, []);\r\n\r\n const handleKeyDown = useCallback(\r\n (e: React.KeyboardEvent<HTMLInputElement>) => {\r\n if (e.key === \"Enter\") {\r\n handleSubmit();\r\n } else if (e.key === \"Escape\") {\r\n handleCancel();\r\n }\r\n },\r\n [handleSubmit, handleCancel]\r\n );\r\n\r\n return (\r\n <div className=\"lumir-dropdown-wrapper\" ref={dropdownRef}>\r\n <button\r\n className=\"lumir-toolbar-btn\"\r\n onClick={() => setIsOpen(!isOpen)}\r\n onMouseDown={handleMouseDown}\r\n title=\"링크 삽입\"\r\n type=\"button\"\r\n >\r\n {Icons.link}\r\n </button>\r\n {isOpen && (\r\n <div className=\"lumir-dropdown-menu lumir-link-menu\">\r\n <form onSubmit={handleSubmit} className=\"lumir-link-form\">\r\n <input\r\n ref={inputRef}\r\n type=\"text\"\r\n className=\"lumir-link-input\"\r\n placeholder=\"링크 URL을 입력하세요\"\r\n value={linkUrl}\r\n onChange={(e) => {\r\n setLinkUrl(e.target.value);\r\n setErrorMsg(null);\r\n }}\r\n onKeyDown={handleKeyDown}\r\n onMouseDown={handleMouseDown}\r\n />\r\n {/* 에러 메시지 표시 */}\r\n {errorMsg && (\r\n <div\r\n style={{\r\n color: \"#dc3545\",\r\n fontSize: \"12px\",\r\n marginTop: \"4px\",\r\n padding: \"0 4px\",\r\n }}\r\n >\r\n {errorMsg}\r\n </div>\r\n )}\r\n <div className=\"lumir-link-actions\">\r\n <button\r\n type=\"button\"\r\n className=\"lumir-link-btn lumir-link-cancel\"\r\n onClick={handleCancel}\r\n onMouseDown={handleMouseDown}\r\n >\r\n 취소\r\n </button>\r\n <button\r\n type=\"submit\"\r\n className=\"lumir-link-btn lumir-link-submit\"\r\n onMouseDown={handleMouseDown}\r\n disabled={!linkUrl.trim()}\r\n >\r\n 확인\r\n </button>\r\n </div>\r\n </form>\r\n </div>\r\n )}\r\n </div>\r\n );\r\n};\r\n","\"use client\";\r\n\r\nimport React, { useCallback } from \"react\";\r\nimport type { EditorType } from \"../../../types\";\r\nimport { Icons } from \"../Icons\";\r\n\r\ninterface TableButtonProps {\r\n editor: EditorType | any;\r\n}\r\n\r\n/**\r\n * 테이블 삽입 버튼\r\n */\r\nexport const TableButton: React.FC<TableButtonProps> = ({ editor }) => {\r\n const handleClick = useCallback(() => {\r\n try {\r\n const block = editor?.getTextCursorPosition()?.block;\r\n if (!block || !editor?.insertBlocks) return;\r\n\r\n // 3x3 기본 테이블 생성\r\n const defaultCell = [{ type: \"text\", text: \"\", styles: {} }];\r\n const tableContent = {\r\n type: \"tableContent\",\r\n rows: [\r\n { cells: [defaultCell, defaultCell, defaultCell] },\r\n { cells: [defaultCell, defaultCell, defaultCell] },\r\n { cells: [defaultCell, defaultCell, defaultCell] },\r\n ],\r\n };\r\n\r\n editor.insertBlocks(\r\n [{ type: \"table\", content: tableContent }] as any,\r\n block,\r\n \"after\"\r\n );\r\n } catch (err) {\r\n console.error(\"Table insert failed:\", err);\r\n }\r\n }, [editor]);\r\n\r\n // 버튼 클릭 시 에디터 포커스/선택 영역 유지\r\n const handleMouseDown = useCallback((e: React.MouseEvent) => {\r\n e.preventDefault();\r\n }, []);\r\n\r\n return (\r\n <button\r\n className=\"lumir-toolbar-btn\"\r\n onClick={handleClick}\r\n onMouseDown={handleMouseDown}\r\n title=\"테이블 삽입\"\r\n type=\"button\"\r\n >\r\n {Icons.table}\r\n </button>\r\n );\r\n};\r\n","\"use client\";\r\n\r\nimport React, { useCallback, useRef } from \"react\";\r\nimport type { EditorType } from \"../../../types\";\r\nimport { Icons } from \"../Icons\";\r\n\r\ninterface HTMLImportButtonProps {\r\n editor: EditorType | any;\r\n}\r\n\r\n/**\r\n * HTML 파일 Import 버튼\r\n */\r\nexport const HTMLImportButton: React.FC<HTMLImportButtonProps> = ({\r\n editor,\r\n}) => {\r\n const fileInputRef = useRef<HTMLInputElement>(null);\r\n\r\n const handleFileUpload = useCallback(\r\n (e: React.ChangeEvent<HTMLInputElement>) => {\r\n const file = e.target.files?.[0];\r\n if (!file) return;\r\n\r\n const reader = new FileReader();\r\n reader.onload = (event) => {\r\n const content = event.target?.result as string;\r\n\r\n try {\r\n if (!editor || !content.trim()) return;\r\n\r\n const block = editor?.getTextCursorPosition()?.block;\r\n if (!block || !editor?.insertBlocks) return;\r\n\r\n // htmlPreview 블록 삽입\r\n editor.insertBlocks(\r\n [\r\n {\r\n type: \"htmlPreview\",\r\n props: {\r\n htmlContent: content,\r\n fileName: file.name,\r\n height: \"400px\",\r\n },\r\n } as any,\r\n ],\r\n block,\r\n \"after\"\r\n );\r\n\r\n // file input 초기화\r\n if (fileInputRef.current) {\r\n fileInputRef.current.value = \"\";\r\n }\r\n } catch (err) {\r\n console.error(\"HTML insert failed:\", err);\r\n }\r\n };\r\n reader.readAsText(file);\r\n },\r\n [editor]\r\n );\r\n\r\n const handleClick = useCallback(() => {\r\n fileInputRef.current?.click();\r\n }, []);\r\n\r\n // 버튼 클릭 시 에디터 포커스/선택 영역 유지\r\n const handleMouseDown = useCallback((e: React.MouseEvent) => {\r\n e.preventDefault();\r\n }, []);\r\n\r\n return (\r\n <>\r\n <input\r\n ref={fileInputRef}\r\n type=\"file\"\r\n accept=\".html,.htm\"\r\n onChange={handleFileUpload}\r\n style={{ display: \"none\" }}\r\n />\r\n <button\r\n className=\"lumir-toolbar-btn\"\r\n onClick={handleClick}\r\n onMouseDown={handleMouseDown}\r\n title=\"HTML Import\"\r\n type=\"button\"\r\n >\r\n {Icons.htmlFile}\r\n </button>\r\n </>\r\n );\r\n};\r\n","\"use client\";\r\n\r\nimport React, { useState, useEffect, useRef, useCallback } from \"react\";\r\nimport type { EditorType } from \"../../../types\";\r\nimport { Icons, BlockTypeIcons } from \"../Icons\";\r\nimport { cn } from \"../../../utils/cn\";\r\n\r\ninterface BlockTypeSelectProps {\r\n editor: EditorType | any;\r\n}\r\n\r\n// 블록 타입 정의\r\ntype BlockTypeItem = {\r\n type: string;\r\n label: string;\r\n icon: string;\r\n level?: number;\r\n isToggle?: boolean;\r\n};\r\n\r\n// 카테고리별 블록 타입\r\nconst blockTypeCategories: { category: string; items: BlockTypeItem[] }[] = [\r\n {\r\n category: \"Headings\",\r\n items: [\r\n { type: \"heading\", label: \"Heading 1\", level: 1, icon: \"h1\", isToggle: false },\r\n { type: \"heading\", label: \"Heading 2\", level: 2, icon: \"h2\", isToggle: false },\r\n { type: \"heading\", label: \"Heading 3\", level: 3, icon: \"h3\", isToggle: false },\r\n { type: \"heading\", label: \"Toggle Heading 1\", level: 1, icon: \"toggleH1\", isToggle: true },\r\n { type: \"heading\", label: \"Toggle Heading 2\", level: 2, icon: \"toggleH2\", isToggle: true },\r\n { type: \"heading\", label: \"Toggle Heading 3\", level: 3, icon: \"toggleH3\", isToggle: true },\r\n ],\r\n },\r\n {\r\n category: \"Basic blocks\",\r\n items: [\r\n { type: \"paragraph\", label: \"Paragraph\", icon: \"paragraph\" },\r\n { type: \"quote\", label: \"Quote\", icon: \"quote\" },\r\n { type: \"codeBlock\", label: \"Code Block\", icon: \"codeBlock\" },\r\n { type: \"bulletListItem\", label: \"Bullet List\", icon: \"bulletList\" },\r\n { type: \"numberedListItem\", label: \"Numbered List\", icon: \"numberedList\" },\r\n { type: \"checkListItem\", label: \"Check List\", icon: \"checkList\" },\r\n { type: \"toggleListItem\", label: \"Toggle List\", icon: \"toggleList\" },\r\n ],\r\n },\r\n];\r\n\r\n// 평탄화된 블록 타입 목록\r\nconst blockTypes: BlockTypeItem[] = blockTypeCategories.flatMap(\r\n (cat) => cat.items\r\n);\r\n\r\n/**\r\n * 블록 타입 선택 드롭다운\r\n */\r\nexport const BlockTypeSelect: React.FC<BlockTypeSelectProps> = ({ editor }) => {\r\n const [isOpen, setIsOpen] = useState(false);\r\n const dropdownRef = useRef<HTMLDivElement>(null);\r\n\r\n // 현재 블록 타입을 직접 계산\r\n const getCurrentBlock = () => {\r\n try {\r\n return editor?.getTextCursorPosition()?.block;\r\n } catch {\r\n return null;\r\n }\r\n };\r\n\r\n const currentBlock = getCurrentBlock();\r\n const currentType = currentBlock?.type || \"paragraph\";\r\n const currentLevel = currentBlock?.props?.level;\r\n const isCurrentToggle =\r\n currentType === \"heading\" && currentBlock?.props?.isToggleable === true;\r\n\r\n useEffect(() => {\r\n const handleClickOutside = (e: MouseEvent) => {\r\n if (\r\n dropdownRef.current &&\r\n !dropdownRef.current.contains(e.target as Node)\r\n ) {\r\n setIsOpen(false);\r\n }\r\n };\r\n document.addEventListener(\"mousedown\", handleClickOutside);\r\n return () => document.removeEventListener(\"mousedown\", handleClickOutside);\r\n }, []);\r\n\r\n const handleTypeChange = (\r\n type: string,\r\n level?: number,\r\n isToggle?: boolean\r\n ) => {\r\n try {\r\n const block = editor?.getTextCursorPosition()?.block;\r\n if (!block || !editor) return;\r\n\r\n const props: any = {};\r\n if (level) props.level = level;\r\n\r\n if (type === \"heading\" && isToggle !== undefined) {\r\n props.isToggleable = isToggle;\r\n editor.updateBlock(block, {\r\n type: \"heading\" as any,\r\n props,\r\n });\r\n } else {\r\n editor.updateBlock(block, { type: type as any, props });\r\n }\r\n\r\n setIsOpen(false);\r\n } catch (err) {\r\n console.error(\"Block type change failed:\", err);\r\n }\r\n };\r\n\r\n const handleMouseDown = useCallback((e: React.MouseEvent) => {\r\n e.preventDefault();\r\n }, []);\r\n\r\n const getCurrentLabel = () => {\r\n if (currentType === \"heading\" && currentLevel) {\r\n const found = blockTypes.find(\r\n (bt) =>\r\n bt.type === \"heading\" &&\r\n bt.level === currentLevel &&\r\n bt.isToggle === isCurrentToggle\r\n );\r\n return found?.label || \"Heading\";\r\n }\r\n const found = blockTypes.find((bt) => bt.type === currentType);\r\n return found?.label || \"Paragraph\";\r\n };\r\n\r\n const getCurrentIcon = () => {\r\n if (currentType === \"heading\" && currentLevel) {\r\n const found = blockTypes.find(\r\n (bt) =>\r\n bt.type === \"heading\" &&\r\n bt.level === currentLevel &&\r\n bt.isToggle === isCurrentToggle\r\n );\r\n return found?.icon || `h${currentLevel}`;\r\n }\r\n const found = blockTypes.find((bt) => bt.type === currentType);\r\n return found?.icon || \"paragraph\";\r\n };\r\n\r\n const isActiveItem = (bt: BlockTypeItem) => {\r\n if (bt.type === \"heading\" && bt.level) {\r\n const isLevelMatch =\r\n currentType === \"heading\" && currentLevel === bt.level;\r\n const isToggleMatch = bt.isToggle === isCurrentToggle;\r\n return isLevelMatch && isToggleMatch;\r\n }\r\n return currentType === bt.type;\r\n };\r\n\r\n return (\r\n <div className=\"lumir-dropdown-wrapper\" ref={dropdownRef}>\r\n <button\r\n className=\"lumir-dropdown-btn lumir-block-type-btn\"\r\n onClick={() => setIsOpen(!isOpen)}\r\n onMouseDown={handleMouseDown}\r\n type=\"button\"\r\n >\r\n <span className=\"lumir-block-icon\">\r\n {BlockTypeIcons[getCurrentIcon()]}\r\n </span>\r\n <span className=\"lumir-block-label\">{getCurrentLabel()}</span>\r\n {Icons.expandMore}\r\n </button>\r\n {isOpen && (\r\n <div className=\"lumir-dropdown-menu lumir-block-menu\">\r\n {blockTypeCategories.map((category) => (\r\n <div key={category.category} className=\"lumir-block-category\">\r\n <div className=\"lumir-block-category-title\">\r\n {category.category}\r\n </div>\r\n {category.items.map((bt) => (\r\n <button\r\n key={bt.icon}\r\n className={cn(\r\n \"lumir-dropdown-item lumir-block-item\",\r\n isActiveItem(bt) && \"is-active\"\r\n )}\r\n onClick={() => handleTypeChange(bt.type, bt.level, bt.isToggle)}\r\n onMouseDown={handleMouseDown}\r\n >\r\n <span className=\"lumir-block-icon\">\r\n {BlockTypeIcons[bt.icon]}\r\n </span>\r\n <span className=\"lumir-block-item-title\">{bt.label}</span>\r\n </button>\r\n ))}\r\n </div>\r\n ))}\r\n </div>\r\n )}\r\n </div>\r\n );\r\n};\r\n","/**\r\n * LumirEditor 커스텀 에러 클래스\r\n */\r\n\r\nexport type LumirErrorCode =\r\n | \"UPLOAD_FAILED\"\r\n | \"INVALID_FILE_TYPE\"\r\n | \"S3_CONFIG_ERROR\"\r\n | \"PRESIGNED_URL_ERROR\"\r\n | \"NETWORK_ERROR\"\r\n | \"EDITOR_ERROR\"\r\n | \"UNKNOWN_ERROR\";\r\n\r\nexport interface LumirErrorDetails {\r\n code: LumirErrorCode;\r\n originalError?: Error;\r\n context?: Record<string, unknown>;\r\n}\r\n\r\n/**\r\n * LumirEditor에서 발생하는 에러를 위한 커스텀 에러 클래스\r\n */\r\nexport class LumirEditorError extends Error {\r\n public readonly code: LumirErrorCode;\r\n public readonly originalError?: Error;\r\n public readonly context?: Record<string, unknown>;\r\n\r\n constructor(message: string, details: Partial<LumirErrorDetails> = {}) {\r\n super(message);\r\n this.name = \"LumirEditorError\";\r\n this.code = details.code || \"UNKNOWN_ERROR\";\r\n this.originalError = details.originalError;\r\n this.context = details.context;\r\n\r\n // Error 클래스 확장 시 프로토타입 체인 유지\r\n Object.setPrototypeOf(this, LumirEditorError.prototype);\r\n }\r\n\r\n /**\r\n * 에러 정보를 JSON 형태로 반환\r\n */\r\n toJSON(): Record<string, unknown> {\r\n return {\r\n name: this.name,\r\n message: this.message,\r\n code: this.code,\r\n context: this.context,\r\n stack: this.stack,\r\n };\r\n }\r\n\r\n /**\r\n * 사용자 친화적 에러 메시지 반환\r\n */\r\n getUserMessage(): string {\r\n switch (this.code) {\r\n case \"UPLOAD_FAILED\":\r\n return \"파일 업로드에 실패했습니다. 다시 시도해주세요.\";\r\n case \"INVALID_FILE_TYPE\":\r\n return \"지원하지 않는 파일 형식입니다. 이미지 파일만 업로드 가능합니다.\";\r\n case \"S3_CONFIG_ERROR\":\r\n return \"S3 설정이 올바르지 않습니다. 관리자에게 문의하세요.\";\r\n case \"PRESIGNED_URL_ERROR\":\r\n return \"업로드 URL 생성에 실패했습니다. 다시 시도해주세요.\";\r\n case \"NETWORK_ERROR\":\r\n return \"네트워크 연결을 확인해주세요.\";\r\n case \"EDITOR_ERROR\":\r\n return \"에디터 오류가 발생했습니다. 페이지를 새로고침해주세요.\";\r\n default:\r\n return \"알 수 없는 오류가 발생했습니다.\";\r\n }\r\n }\r\n\r\n /**\r\n * 일반 Error를 LumirEditorError로 변환\r\n */\r\n static fromError(\r\n error: Error,\r\n code: LumirErrorCode = \"UNKNOWN_ERROR\",\r\n context?: Record<string, unknown>\r\n ): LumirEditorError {\r\n return new LumirEditorError(error.message, {\r\n code,\r\n originalError: error,\r\n context,\r\n });\r\n }\r\n\r\n /**\r\n * 업로드 실패 에러 생성\r\n */\r\n static uploadFailed(\r\n message: string,\r\n originalError?: Error\r\n ): LumirEditorError {\r\n return new LumirEditorError(message, {\r\n code: \"UPLOAD_FAILED\",\r\n originalError,\r\n });\r\n }\r\n\r\n /**\r\n * 잘못된 파일 형식 에러 생성\r\n * @param allowVideoUpload true이면 \"image and video\" 메시지 사용\r\n */\r\n static invalidFileType(\r\n fileName: string,\r\n allowVideoUpload?: boolean\r\n ): LumirEditorError {\r\n const message =\r\n allowVideoUpload === true\r\n ? `Invalid file type: ${fileName}. Only image and video files are allowed.`\r\n : `Invalid file type: ${fileName}. Only image files are allowed.`;\r\n return new LumirEditorError(message, {\r\n code: \"INVALID_FILE_TYPE\",\r\n context: { fileName },\r\n });\r\n }\r\n\r\n /**\r\n * S3 설정 에러 생성\r\n */\r\n static s3ConfigError(message: string): LumirEditorError {\r\n return new LumirEditorError(message, {\r\n code: \"S3_CONFIG_ERROR\",\r\n });\r\n }\r\n\r\n /**\r\n * 네트워크 에러 생성\r\n */\r\n static networkError(originalError?: Error): LumirEditorError {\r\n return new LumirEditorError(\"Network request failed\", {\r\n code: \"NETWORK_ERROR\",\r\n originalError,\r\n });\r\n }\r\n}\r\n","import { Extension } from \"@tiptap/core\";\r\n\r\nexport type VerticalAlignment = \"top\" | \"middle\" | \"bottom\";\r\n\r\nexport const VerticalAlignmentExtension = Extension.create({\r\n name: \"verticalAlignment\",\r\n\r\n addGlobalAttributes() {\r\n return [\r\n {\r\n types: [\"tableCell\", \"tableHeader\"],\r\n attributes: {\r\n verticalAlignment: {\r\n default: \"top\",\r\n parseHTML: (element) => {\r\n return (\r\n element.getAttribute(\"data-vertical-alignment\") || \"top\"\r\n );\r\n },\r\n renderHTML: (attributes) => {\r\n if (\r\n !attributes.verticalAlignment ||\r\n attributes.verticalAlignment === \"top\"\r\n ) {\r\n return {};\r\n }\r\n return {\r\n \"data-vertical-alignment\": attributes.verticalAlignment,\r\n };\r\n },\r\n },\r\n },\r\n },\r\n ];\r\n },\r\n\r\n addProseMirrorPlugins() {\r\n return [];\r\n },\r\n});\r\n","import { Extension } from \"@tiptap/core\";\r\nimport { rowResizing } from \"./rowResizing\";\r\nimport { tableScaling } from \"./tableScaling\";\r\nimport { tableCellAttrPreserve } from \"./tableCellAttrPreserve\";\r\n\r\nexport interface RowHeightOptions {\r\n /**\r\n * 행 경계 드래그 리사이즈(rowResizing 플러그인) 활성화 여부.\r\n * false면 속성 등록/렌더링은 유지하되 드래그 UI만 비활성화한다\r\n * (= 저장된 행 높이는 그대로 표시되지만 사용자가 조절할 수 없음).\r\n * LumirEditor에서 `tableHandles` prop으로 게이트한다.\r\n */\r\n resizable: boolean;\r\n}\r\n\r\n/**\r\n * 표 셀에 `rowHeight`(px) 노드 attr을 추가하는 Tiptap 확장.\r\n *\r\n * BlockNote 0.35는 행 높이를 모델링하지 않으므로(`TableContent`에 `columnWidths`만 존재),\r\n * `verticalAlignment`와 동일한 패턴으로 셀 단위 글로벌 attribute를 등록한다:\r\n * - renderHTML이 inline `style=\"height:Npx\"`를 출력 → HTML 복사/내보내기/Excel 붙여넣기에 라운드트립\r\n * - parseHTML이 `style.height` 또는 `data-row-height`를 역파싱\r\n * - 저장(블록 JSON 주입)은 utils/table-cell-props.ts의 injectTableCellAttrs가 담당\r\n * - 로드는 BlockNote blockToNode가 cell.props를 노드 attr로 펼쳐 자동 복원\r\n *\r\n * 드래그 리사이즈 UI는 addProseMirrorPlugins에서 rowResizing 플러그인으로 제공하며,\r\n * resizable 옵션으로 게이트한다.\r\n */\r\nexport const RowHeightExtension = Extension.create<RowHeightOptions>({\r\n name: \"rowHeight\",\r\n\r\n addOptions() {\r\n return { resizable: true };\r\n },\r\n\r\n addGlobalAttributes() {\r\n return [\r\n {\r\n types: [\"tableCell\", \"tableHeader\"],\r\n attributes: {\r\n rowHeight: {\r\n default: null,\r\n parseHTML: (element) => {\r\n const fromStyle = parseInt(element.style?.height ?? \"\", 10);\r\n if (Number.isFinite(fromStyle) && fromStyle > 0) {\r\n return fromStyle;\r\n }\r\n const fromAttr = parseInt(\r\n element.getAttribute(\"data-row-height\") ?? \"\",\r\n 10,\r\n );\r\n return Number.isFinite(fromAttr) && fromAttr > 0\r\n ? fromAttr\r\n : null;\r\n },\r\n renderHTML: (attributes) => {\r\n const h = attributes.rowHeight;\r\n if (!h || typeof h !== \"number\") {\r\n return {};\r\n }\r\n return { style: `height: ${h}px` };\r\n },\r\n },\r\n },\r\n },\r\n ];\r\n },\r\n\r\n addProseMirrorPlugins() {\r\n // 커스텀 셀 attr 보존은 항상(드래그 비활성이어도 행/열 추가 등으로 유실 방지).\r\n // 드래그 리사이즈 UI만 resizable로 게이트.\r\n const plugins = [tableCellAttrPreserve()];\r\n if (this.options.resizable) {\r\n plugins.push(rowResizing());\r\n // 표 전체 종횡비 고정 스케일(우하단 코너 드래그).\r\n plugins.push(tableScaling());\r\n }\r\n return plugins;\r\n },\r\n});\r\n","/**\r\n * 표 **행 높이(row height)** 드래그 리사이즈 ProseMirror 플러그인.\r\n *\r\n * prosemirror-tables의 `columnResizing`(열 너비)을 **수직축으로 미러링**한 구현이다.\r\n * columnResizing이 없는 행 버전을 동일한 구조로 직접 작성한다:\r\n *\r\n * - 아무 행의 **아래 경계**(또는 위 경계)에 hover → `row-resize` 커서 + 핸들 노출\r\n * - 경계를 드래그 → 대상 행 높이를 **실시간 프리뷰**(셀에 height 데코레이션)\r\n * - 드롭 → 해당 행의 모든 셀에 `rowHeight` 노드 attr을 `setNodeMarkup`으로 일괄 커밋\r\n *\r\n * 저장은 셀 단위 `rowHeight` 속성(RowHeightExtension의 글로벌 attribute)으로 이뤄지며,\r\n * 이는 BlockNote가 행 props를 모델링하지 않기 때문이다(행 높이 = 행 셀들의 max height).\r\n *\r\n * 라이브 프리뷰 방식(중요):\r\n * 열 리사이즈는 `<colgroup>`을 직접 조작해 프리뷰한다(BlockNoteTableView.ignoreMutation이\r\n * colgroup 변경을 무시하므로 PM이 되돌리지 않음). 행에는 colgroup 등가물이 없고, 셀/행의\r\n * style을 직접 바꾸면 PM의 MutationObserver가 노드를 즉시 다시 그려 초기화한다.\r\n * → 그래서 드래그 중 높이를 **Decoration.node({style:'height:Npx'})**로 입힌다(PM이 직접\r\n * 렌더하므로 mutation 충돌 없음). 높이 갱신은 **meta-only 트랜잭션**으로 하여 doc을 바꾸지\r\n * 않으므로 onContentChange/undo 스팸이 없다. 드롭 시 한 번만 실제 커밋(단일 undo 스텝).\r\n *\r\n * ⚠️ prosemirror-state/view/tables는 반드시 에디터(@blocknote)와 **동일한 인스턴스**를\r\n * 써야 한다(Decoration/Plugin instanceof). 따라서 raw `prosemirror-*`에서 import하고\r\n * tsup 빌드에서 external 처리한다(@blocknote가 같은 패키지를 런타임 의존성으로 쓰므로\r\n * 소비자 트리에서 단일 복사본으로 dedup된다).\r\n */\r\n\r\nimport { Plugin, PluginKey } from \"prosemirror-state\";\r\nimport type { EditorState } from \"prosemirror-state\";\r\nimport { Decoration, DecorationSet } from \"prosemirror-view\";\r\nimport type { EditorView } from \"prosemirror-view\";\r\nimport { TableMap, cellAround, pointsAtCell } from \"prosemirror-tables\";\r\nimport {\r\n ROW_RESIZE_HANDLE_WIDTH,\r\n ROW_RESIZE_MIN_HEIGHT,\r\n} from \"../constants/limits\";\r\n\r\nexport const rowResizingPluginKey = new PluginKey<RowResizeState>(\r\n \"lumirRowResizing\",\r\n);\r\n\r\ntype Dragging = {\r\n startY: number;\r\n startHeight: number;\r\n /** 현재 드래그 위치 기준 프리뷰 높이(px). 데코레이션이 이 값을 렌더한다. */\r\n currentHeight: number;\r\n};\r\n\r\n/**\r\n * 플러그인 상태. activeHandle = 리사이즈 대상 셀의 pos(없으면 -1),\r\n * dragging = 드래그 중이면 시작 좌표/높이/현재 프리뷰 높이, 아니면 null.\r\n */\r\nexport class RowResizeState {\r\n constructor(\r\n public activeHandle: number,\r\n public dragging: Dragging | null,\r\n ) {}\r\n\r\n apply(tr: any): RowResizeState {\r\n const action = tr.getMeta(rowResizingPluginKey);\r\n if (action && action.setHandle != null) {\r\n return new RowResizeState(action.setHandle, null);\r\n }\r\n if (action && action.setDragging !== undefined) {\r\n return new RowResizeState(this.activeHandle, action.setDragging);\r\n }\r\n if (this.activeHandle > -1 && tr.docChanged) {\r\n let handle = tr.mapping.map(this.activeHandle, -1);\r\n if (!pointsAtCell(tr.doc.resolve(handle))) {\r\n handle = -1;\r\n }\r\n return new RowResizeState(handle, this.dragging);\r\n }\r\n return this;\r\n }\r\n}\r\n\r\nexport function rowResizing({\r\n handleWidth = ROW_RESIZE_HANDLE_WIDTH,\r\n minHeight = ROW_RESIZE_MIN_HEIGHT,\r\n}: { handleWidth?: number; minHeight?: number } = {}): Plugin {\r\n return new Plugin<RowResizeState>({\r\n key: rowResizingPluginKey,\r\n state: {\r\n init() {\r\n return new RowResizeState(-1, null);\r\n },\r\n apply(tr, prev) {\r\n return prev.apply(tr);\r\n },\r\n },\r\n props: {\r\n attributes: (state): Record<string, string> => {\r\n const pluginState = rowResizingPluginKey.getState(state);\r\n return pluginState && pluginState.activeHandle > -1\r\n ? { class: \"row-resize-cursor\" }\r\n : {};\r\n },\r\n handleDOMEvents: {\r\n mousemove: (view, event) => {\r\n handleMouseMove(view, event as MouseEvent, handleWidth);\r\n },\r\n mouseleave: (view) => {\r\n handleMouseLeave(view);\r\n },\r\n mousedown: (view, event) => {\r\n handleMouseDown(view, event as MouseEvent, minHeight);\r\n },\r\n },\r\n decorations: (state) => {\r\n const pluginState = rowResizingPluginKey.getState(state);\r\n if (pluginState && pluginState.activeHandle > -1) {\r\n return handleDecorations(state, pluginState);\r\n }\r\n return undefined;\r\n },\r\n },\r\n });\r\n}\r\n\r\nfunction handleMouseMove(\r\n view: EditorView,\r\n event: MouseEvent,\r\n handleWidth: number,\r\n) {\r\n if (!view.editable) {\r\n return;\r\n }\r\n const pluginState = rowResizingPluginKey.getState(view.state);\r\n if (!pluginState) {\r\n return;\r\n }\r\n if (!pluginState.dragging) {\r\n const target = domCellAround(event.target as Node | null);\r\n let cell = -1;\r\n if (target) {\r\n const { top, bottom } = (target as HTMLElement).getBoundingClientRect();\r\n if (event.clientY - top <= handleWidth) {\r\n cell = edgeCell(view, event, \"top\", handleWidth);\r\n } else if (bottom - event.clientY <= handleWidth) {\r\n cell = edgeCell(view, event, \"bottom\", handleWidth);\r\n }\r\n }\r\n if (cell !== pluginState.activeHandle) {\r\n updateHandle(view, cell);\r\n }\r\n }\r\n}\r\n\r\nfunction handleMouseLeave(view: EditorView) {\r\n if (!view.editable) {\r\n return;\r\n }\r\n const pluginState = rowResizingPluginKey.getState(view.state);\r\n if (pluginState && pluginState.activeHandle > -1 && !pluginState.dragging) {\r\n updateHandle(view, -1);\r\n }\r\n}\r\n\r\nfunction handleMouseDown(\r\n view: EditorView,\r\n event: MouseEvent,\r\n minHeight: number,\r\n): boolean {\r\n if (!view.editable) {\r\n return false;\r\n }\r\n const win = view.dom.ownerDocument.defaultView ?? window;\r\n const pluginState = rowResizingPluginKey.getState(view.state);\r\n if (!pluginState || pluginState.activeHandle === -1 || pluginState.dragging) {\r\n return false;\r\n }\r\n\r\n const startHeight = currentRowHeight(view, pluginState.activeHandle);\r\n setDragging(view, {\r\n startY: event.clientY,\r\n startHeight,\r\n currentHeight: startHeight,\r\n });\r\n\r\n function finish(finishEvent: MouseEvent) {\r\n win.removeEventListener(\"mouseup\", finish);\r\n win.removeEventListener(\"mousemove\", move);\r\n const ps = rowResizingPluginKey.getState(view.state);\r\n if (ps?.dragging) {\r\n const finalHeight = draggedHeight(ps.dragging, finishEvent, minHeight);\r\n // ⚠️ 순서 중요: 프리뷰 데코(셀 style:height)를 먼저 제거한 뒤 커밋한다.\r\n // 데코의 inline style과 커밋 후 renderHTML의 style이 같은 style 속성을 두고\r\n // 충돌하는데, 커밋 후 데코를 제거하면 PM이 style을 비우고 renderHTML 값을\r\n // 복원하지 않는다(빈 style). 데코를 먼저 없애면 커밋 시 renderHTML이 깨끗이\r\n // 적용된다. 두 dispatch는 동기로 연달아 일어나 중간 페인트가 없어 깜빡임도 없다.\r\n setDragging(view, null);\r\n commitRowHeight(view, ps.activeHandle, finalHeight);\r\n }\r\n }\r\n\r\n function move(moveEvent: MouseEvent) {\r\n // 버튼이 떼어진 채 들어온 mousemove면(놓친 mouseup 안전장치) 즉시 마무리.\r\n // 표준 `buttons`(0=눌림 없음)를 우선 보고, 레거시 `which`를 폴백으로 본다.\r\n if (\r\n moveEvent.buttons === 0 ||\r\n (moveEvent.buttons === undefined && !moveEvent.which)\r\n ) {\r\n return finish(moveEvent);\r\n }\r\n const ps = rowResizingPluginKey.getState(view.state);\r\n if (!ps?.dragging) {\r\n return;\r\n }\r\n const h = draggedHeight(ps.dragging, moveEvent, minHeight);\r\n if (h !== ps.dragging.currentHeight) {\r\n // doc은 바꾸지 않고 프리뷰 높이만 갱신(meta-only) → 데코레이션이 즉시 반영.\r\n setDragging(view, { ...ps.dragging, currentHeight: h });\r\n }\r\n }\r\n\r\n win.addEventListener(\"mouseup\", finish);\r\n win.addEventListener(\"mousemove\", move);\r\n event.preventDefault();\r\n return true;\r\n}\r\n\r\n/** 드래그 상태를 갱신하는 meta-only 트랜잭션(doc 미변경). */\r\nfunction setDragging(view: EditorView, dragging: Dragging | null) {\r\n view.dispatch(\r\n view.state.tr.setMeta(rowResizingPluginKey, { setDragging: dragging }),\r\n );\r\n}\r\n\r\n/** 렌더된 `<tr>` 높이를 드래그 시작 높이로 쓴다(미설정 행도 자연스러운 시작점). */\r\nfunction currentRowHeight(view: EditorView, cellPos: number): number {\r\n const info = targetRowInfo(view, cellPos);\r\n const tr = rowTrElement(view, cellPos, info.row);\r\n return tr ? tr.offsetHeight : ROW_RESIZE_MIN_HEIGHT;\r\n}\r\n\r\n/** 드롭 시 커밋: 대상 행에서 **끝나는** 모든 셀에 rowHeight attr 일괄 적용(기록 O). */\r\nfunction commitRowHeight(view: EditorView, cellPos: number, height: number) {\r\n const { table, map, start, row } = targetRowInfo(view, cellPos);\r\n const tr = view.state.tr;\r\n const seen = new Set<number>();\r\n for (let col = 0; col < map.width; col++) {\r\n const cellRelPos = map.map[row * map.width + col];\r\n if (seen.has(cellRelPos)) {\r\n continue; // colspan: 같은 셀 중복 스킵\r\n }\r\n seen.add(cellRelPos);\r\n const rect = map.findCell(cellRelPos);\r\n // rowspan 셀이 이 행에서 끝나지 않으면(아래로 더 뻗으면) origin 행 높이를 유지\r\n if (rect.bottom - 1 !== row) {\r\n continue;\r\n }\r\n const node = table.nodeAt(cellRelPos);\r\n if (!node || node.attrs.rowHeight === height) {\r\n continue;\r\n }\r\n tr.setNodeMarkup(start + cellRelPos, undefined, {\r\n ...node.attrs,\r\n rowHeight: height,\r\n });\r\n }\r\n if (tr.docChanged) {\r\n view.dispatch(tr);\r\n }\r\n}\r\n\r\n/** activeHandle 셀이 속한 테이블/맵/대상 행 인덱스(이 셀의 아래 경계가 위치한 행)를 구한다. */\r\nfunction targetRowInfo(view: EditorView, cellPos: number) {\r\n const $cell = view.state.doc.resolve(cellPos);\r\n const table = $cell.node(-1);\r\n const map = TableMap.get(table);\r\n const start = $cell.start(-1);\r\n const rect = map.findCell($cell.pos - start);\r\n return { table, map, start, row: rect.bottom - 1, $cell };\r\n}\r\n\r\n/** activeHandle 셀이 속한 `<table>`을 찾아 그 안의 row번째 `<tr>`을 반환. */\r\nfunction rowTrElement(\r\n view: EditorView,\r\n cellPos: number,\r\n row: number,\r\n): HTMLTableRowElement | null {\r\n let dom: Node | null = view.nodeDOM(cellPos) as Node | null;\r\n while (dom && dom.nodeName !== \"TABLE\") {\r\n dom = dom.parentNode;\r\n }\r\n if (!dom) {\r\n return null;\r\n }\r\n return (dom as HTMLTableElement).rows[row] ?? null;\r\n}\r\n\r\nfunction domCellAround(target: Node | null): HTMLElement | null {\r\n let node: any = target;\r\n while (node && node.nodeName !== \"TD\" && node.nodeName !== \"TH\") {\r\n node = node.classList?.contains(\"ProseMirror\") ? null : node.parentNode;\r\n }\r\n return node as HTMLElement | null;\r\n}\r\n\r\n/**\r\n * 경계에 인접한 셀 pos를 구한다.\r\n * - \"bottom\": 커서가 닿은 셀(이 셀의 아래 경계를 드래그)\r\n * - \"top\": 윗 행의 셀(그 셀의 아래 경계 = 이 경계). map index - width.\r\n */\r\nfunction edgeCell(\r\n view: EditorView,\r\n event: MouseEvent,\r\n side: \"top\" | \"bottom\",\r\n handleWidth: number,\r\n): number {\r\n const offset = side === \"bottom\" ? -handleWidth : handleWidth;\r\n const found = view.posAtCoords({\r\n left: event.clientX,\r\n top: event.clientY + offset,\r\n });\r\n if (!found) {\r\n return -1;\r\n }\r\n const $cell = cellAround(view.state.doc.resolve(found.pos));\r\n if (!$cell) {\r\n return -1;\r\n }\r\n if (side === \"bottom\") {\r\n return $cell.pos;\r\n }\r\n const map = TableMap.get($cell.node(-1));\r\n const start = $cell.start(-1);\r\n const index = map.map.indexOf($cell.pos - start);\r\n return index < map.width ? -1 : start + map.map[index - map.width];\r\n}\r\n\r\nfunction updateHandle(view: EditorView, value: number) {\r\n view.dispatch(\r\n view.state.tr.setMeta(rowResizingPluginKey, { setHandle: value }),\r\n );\r\n}\r\n\r\nfunction draggedHeight(\r\n dragging: Dragging,\r\n event: MouseEvent,\r\n minHeight: number,\r\n): number {\r\n const offset = event.clientY - dragging.startY;\r\n return Math.max(minHeight, dragging.startHeight + offset);\r\n}\r\n\r\n/**\r\n * activeHandle이 가리키는 행의 각 셀에 데코레이션을 그린다.\r\n * - 하단 경계 핸들 위젯(.row-resize-handle)\r\n * - 드래그 중이면: 셀에 height 인라인 style 데코레이션(라이브 프리뷰) + dragging 클래스\r\n */\r\nfunction handleDecorations(\r\n state: EditorState,\r\n pluginState: RowResizeState,\r\n): DecorationSet {\r\n const decorations: Decoration[] = [];\r\n const $cell = state.doc.resolve(pluginState.activeHandle);\r\n const table = $cell.node(-1);\r\n if (!table) {\r\n return DecorationSet.empty;\r\n }\r\n const map = TableMap.get(table);\r\n const start = $cell.start(-1);\r\n const row = map.findCell($cell.pos - start).bottom - 1;\r\n const dragging = pluginState.dragging;\r\n const seen = new Set<number>();\r\n\r\n for (let col = 0; col < map.width; col++) {\r\n const cellRelPos = map.map[row * map.width + col];\r\n if (seen.has(cellRelPos)) {\r\n continue;\r\n }\r\n seen.add(cellRelPos);\r\n if (map.findCell(cellRelPos).bottom - 1 !== row) {\r\n continue;\r\n }\r\n const node = table.nodeAt(cellRelPos);\r\n if (!node) {\r\n continue;\r\n }\r\n const from = start + cellRelPos;\r\n const to = from + node.nodeSize;\r\n\r\n if (dragging) {\r\n // 라이브 프리뷰: 셀에 height를 입혀 마우스를 따라 즉시 커진다(PM이 직접 렌더).\r\n decorations.push(\r\n Decoration.node(from, to, {\r\n class: \"row-resize-dragging\",\r\n style: `height: ${dragging.currentHeight}px`,\r\n }),\r\n );\r\n }\r\n\r\n const handle = document.createElement(\"div\");\r\n handle.className = \"row-resize-handle\";\r\n decorations.push(Decoration.widget(to - 1, handle));\r\n }\r\n\r\n return DecorationSet.create(state.doc, decorations);\r\n}\r\n","/**\r\n * 보안 및 성능 제한 상수\r\n */\r\n\r\n/** 최대 파일 크기: 10MB (이미지) */\r\nexport const MAX_FILE_SIZE = 10 * 1024 * 1024;\r\n\r\n/** 최대 동영상 파일 크기: 100MB */\r\nexport const MAX_VIDEO_FILE_SIZE = 100 * 1024 * 1024;\r\n\r\n/** 업로드 타임아웃: 30초 (이미지 등) */\r\nexport const UPLOAD_TIMEOUT = 30000;\r\n\r\n/** 대용량 파일(비디오 등) 업로드 타임아웃: 120초 */\r\nexport const UPLOAD_TIMEOUT_VIDEO = 120000;\r\n\r\n/** 허용된 이미지 MIME 타입 (SVG 제외) */\r\nexport const ALLOWED_IMAGE_MIME_TYPES = new Set([\r\n \"image/jpeg\",\r\n \"image/png\",\r\n \"image/gif\",\r\n \"image/webp\",\r\n \"image/bmp\",\r\n]);\r\n\r\n/** 차단된 파일 확장자 */\r\nexport const BLOCKED_EXTENSIONS = [\".svg\", \".svgz\"];\r\n\r\n/** 허용된 이미지 확장자 */\r\nexport const ALLOWED_IMAGE_EXTENSIONS = [\r\n \".png\",\r\n \".jpg\",\r\n \".jpeg\",\r\n \".gif\",\r\n \".webp\",\r\n \".bmp\",\r\n];\r\n\r\n/** 허용된 동영상 MIME 타입 */\r\nexport const ALLOWED_VIDEO_MIME_TYPES = new Set([\r\n \"video/mp4\",\r\n \"video/webm\",\r\n \"video/ogg\",\r\n \"video/quicktime\", // .mov\r\n]);\r\n\r\n/** 허용된 동영상 확장자 */\r\nexport const ALLOWED_VIDEO_EXTENSIONS = [\r\n \".mp4\",\r\n \".webm\",\r\n \".ogg\",\r\n \".mov\",\r\n];\r\n\r\n/** 표 행 높이 리사이즈: 최소 행 높이(px) */\r\nexport const ROW_RESIZE_MIN_HEIGHT = 24;\r\n\r\n/** 표 행 높이 리사이즈: 행 경계 hit-area 두께(px). columnResizing handleWidth(5)와 동일. */\r\nexport const ROW_RESIZE_HANDLE_WIDTH = 5;\r\n\r\n/** 표 전체 스케일(코너 드래그): 우하단 코너 hit-area 한 변(px). */\r\nexport const TABLE_SCALE_CORNER_SIZE = 16;\r\n\r\n/** 표 전체 스케일: 최소 열 너비(px). prosemirror-tables cellMinWidth(25) 근사. */\r\nexport const TABLE_SCALE_MIN_COL_WIDTH = 24;\r\n\r\n/** 표 전체 스케일: 최대 배율(런어웨이 방지). */\r\nexport const TABLE_SCALE_MAX = 6;\r\n","/**\r\n * 표 **전체 스케일**(우하단 코너 드래그)의 프리뷰/커밋 엔진 ProseMirror 플러그인.\r\n *\r\n * UI 핸들과 드래그 추적은 LumirTableHandlesController(에디터 DOM 밖 오버레이)가 담당한다.\r\n * 코너가 rowResizing(하단 경계)·columnResizing(우측 경계) 핸들과 겹쳐 mousedown이 그쪽으로\r\n * 새는 것을 피하기 위해, 핸들은 에디터 DOM 밖에 두고 자체 포인터 이벤트로 처리한다.\r\n * 이 플러그인은 컨트롤러가 dispatch한 프리뷰 상태를 받아:\r\n * - 높이: 셀 `Decoration.node({style:height})` 라이브 프리뷰\r\n * - 너비: 표 `<colgroup>` `<col>` width 직접 조작(BlockNoteTableView.ignoreMutation이 무시)\r\n * 로 그리고, 커밋 시 모든 셀의 colwidth/rowHeight를 일괄 setNodeMarkup(단일 undo)한다.\r\n * 갱신은 meta-only 트랜잭션이라 undo/onContentChange 스팸이 없다.\r\n *\r\n * ⚠️ prosemirror-* 는 @blocknote와 동일 인스턴스를 써야 한다(rowResizing와 동일 이유).\r\n */\r\n\r\nimport { Plugin, PluginKey } from \"prosemirror-state\";\r\nimport type { EditorState } from \"prosemirror-state\";\r\nimport { Decoration, DecorationSet } from \"prosemirror-view\";\r\nimport type { EditorView } from \"prosemirror-view\";\r\nimport { TableMap } from \"prosemirror-tables\";\r\n\r\nexport const tableScalingPluginKey = new PluginKey<TableScalePreview | null>(\r\n \"lumirTableScaling\",\r\n);\r\n\r\nexport type TableScalePreview = {\r\n /** 대상 table 노드 pos. */\r\n tablePos: number;\r\n /** 드래그 시작 시점의 열 너비/행 높이(px) — materialize 값. */\r\n colWidths: number[];\r\n rowHeights: number[];\r\n /** 드래그 시작 시점의 표 렌더 W×H(px). */\r\n origW: number;\r\n origH: number;\r\n /** 현재 배율(≥1 또는 <1). */\r\n scale: number;\r\n};\r\n\r\nexport function tableScaling(): Plugin<TableScalePreview | null> {\r\n return new Plugin<TableScalePreview | null>({\r\n key: tableScalingPluginKey,\r\n state: {\r\n init: () => null,\r\n apply(tr, prev) {\r\n const meta = tr.getMeta(tableScalingPluginKey);\r\n if (meta !== undefined) return meta.preview as TableScalePreview | null;\r\n if (prev && tr.docChanged) {\r\n return { ...prev, tablePos: tr.mapping.map(prev.tablePos, -1) };\r\n }\r\n return prev;\r\n },\r\n },\r\n props: {\r\n decorations(state) {\r\n const p = tableScalingPluginKey.getState(state);\r\n return p ? buildHeightDecorations(state, p) : null;\r\n },\r\n },\r\n view: (view) => ({\r\n update: () => {\r\n const p = tableScalingPluginKey.getState(view.state);\r\n if (p) applyColgroupPreview(view, p);\r\n },\r\n }),\r\n });\r\n}\r\n\r\n/** 컨트롤러가 호출: 프리뷰 상태 설정/해제(meta-only). */\r\nexport function setTableScalePreview(\r\n view: EditorView,\r\n preview: TableScalePreview | null,\r\n): void {\r\n view.dispatch(view.state.tr.setMeta(tableScalingPluginKey, { preview }));\r\n}\r\n\r\n/** 컨트롤러가 호출: 드래그 시작 시 대상 표의 현재 열너비/행높이/렌더크기를 측정. */\r\nexport function measureTableForScale(\r\n view: EditorView,\r\n tablePos: number,\r\n): TableScalePreview | null {\r\n const tableEl = findTableEl(view.nodeDOM(tablePos));\r\n const node = view.state.doc.nodeAt(tablePos);\r\n if (!tableEl || !node || node.type.name !== \"table\") return null;\r\n const map = TableMap.get(node);\r\n const rect = tableEl.getBoundingClientRect();\r\n const body = tableEl.tBodies[0];\r\n const rowHeights = body\r\n ? Array.from(body.rows).map((tr) => tr.getBoundingClientRect().height)\r\n : [];\r\n const colWidths = measureColWidths(tableEl, map.width);\r\n if (rowHeights.length !== map.height || colWidths.length !== map.width) {\r\n return null;\r\n }\r\n return {\r\n tablePos,\r\n colWidths,\r\n rowHeights,\r\n origW: rect.width,\r\n origH: rect.height,\r\n scale: 1,\r\n };\r\n}\r\n\r\n/** 컨트롤러가 호출: 드롭 시 모든 셀의 colwidth/rowHeight를 ×scale로 일괄 커밋(단일 undo). */\r\nexport function commitTableScale(\r\n view: EditorView,\r\n preview: TableScalePreview,\r\n): void {\r\n const { state } = view;\r\n const { tablePos, colWidths, rowHeights, scale } = preview;\r\n const table = state.doc.nodeAt(tablePos);\r\n if (!table || table.type.name !== \"table\") return;\r\n const map = TableMap.get(table);\r\n const start = tablePos + 1;\r\n const tr = state.tr;\r\n const seen = new Set<number>();\r\n\r\n for (const relPos of map.map) {\r\n if (seen.has(relPos)) continue;\r\n seen.add(relPos);\r\n const node = table.nodeAt(relPos);\r\n if (!node) continue;\r\n const rect = map.findCell(relPos);\r\n const colwidth: number[] = [];\r\n for (let c = rect.left; c < rect.right; c++) {\r\n colwidth.push(Math.round((colWidths[c] ?? 0) * scale));\r\n }\r\n let h = 0;\r\n for (let r = rect.top; r < rect.bottom; r++) h += rowHeights[r] ?? 0;\r\n const rowHeight = Math.round(h * scale);\r\n tr.setNodeMarkup(start + relPos, undefined, {\r\n ...node.attrs,\r\n colwidth: colwidth.some((w) => w > 0) ? colwidth : null,\r\n rowHeight: rowHeight > 0 ? rowHeight : null,\r\n });\r\n }\r\n if (tr.docChanged) view.dispatch(tr);\r\n}\r\n\r\n// ── 측정 보조 ─────────────────────────────────────────────────\r\nfunction measureColWidths(tableEl: HTMLTableElement, width: number): number[] {\r\n const widths = new Array(width).fill(0);\r\n const colgroup = tableEl.querySelector(\"colgroup\");\r\n if (colgroup && colgroup.children.length === width) {\r\n let allSet = true;\r\n for (let i = 0; i < width; i++) {\r\n const w = parseFloat((colgroup.children[i] as HTMLElement).style.width);\r\n if (Number.isFinite(w) && w > 0) widths[i] = w;\r\n else allSet = false;\r\n }\r\n if (allSet) return widths;\r\n }\r\n const firstRow = tableEl.tBodies[0]?.rows[0];\r\n if (firstRow) {\r\n let col = 0;\r\n for (const cell of Array.from(firstRow.cells)) {\r\n const span = cell.colSpan || 1;\r\n const w = cell.getBoundingClientRect().width / span;\r\n for (let s = 0; s < span && col < width; s++) widths[col++] = w;\r\n }\r\n }\r\n return widths;\r\n}\r\n\r\n// ── 프리뷰 렌더 ────────────────────────────────────────────────\r\nfunction applyColgroupPreview(view: EditorView, p: TableScalePreview) {\r\n const tableEl = findTableEl(view.nodeDOM(p.tablePos));\r\n const colgroup = tableEl?.querySelector(\"colgroup\");\r\n if (!colgroup) return;\r\n const cols = colgroup.children;\r\n for (let i = 0; i < cols.length && i < p.colWidths.length; i++) {\r\n (cols[i] as HTMLElement).style.width =\r\n Math.round(p.colWidths[i] * p.scale) + \"px\";\r\n }\r\n}\r\n\r\nfunction buildHeightDecorations(\r\n state: EditorState,\r\n p: TableScalePreview,\r\n): DecorationSet {\r\n const table = state.doc.nodeAt(p.tablePos);\r\n if (!table || table.type.name !== \"table\") return DecorationSet.empty;\r\n const map = TableMap.get(table);\r\n const start = p.tablePos + 1;\r\n const decorations: Decoration[] = [];\r\n const seen = new Set<number>();\r\n for (const relPos of map.map) {\r\n if (seen.has(relPos)) continue;\r\n seen.add(relPos);\r\n const node = table.nodeAt(relPos);\r\n if (!node) continue;\r\n const rect = map.findCell(relPos);\r\n let h = 0;\r\n for (let r = rect.top; r < rect.bottom; r++) h += p.rowHeights[r] ?? 0;\r\n const from = start + relPos;\r\n const to = from + node.nodeSize;\r\n decorations.push(\r\n Decoration.node(from, to, {\r\n class: \"lumir-table-scale-dragging\",\r\n style: `height: ${Math.round(h * p.scale)}px`,\r\n }),\r\n );\r\n }\r\n return DecorationSet.create(state.doc, decorations);\r\n}\r\n\r\n/** 임의 DOM에서 가장 가까운 <table>을 찾는다(nodeDOM이 wrapper일 수 있음). */\r\nfunction findTableEl(dom: Node | null): HTMLTableElement | null {\r\n if (!dom) return null;\r\n const el = dom as HTMLElement;\r\n if (el.nodeName === \"TABLE\") return el as HTMLTableElement;\r\n const inner = el.querySelector?.(\"table\");\r\n if (inner) return inner as HTMLTableElement;\r\n return (el.closest?.(\"table\") as HTMLTableElement) ?? null;\r\n}\r\n","/**\r\n * 표 구조 편집 시 커스텀 셀 attr(rowHeight, verticalAlignment) 유실을 막는 플러그인.\r\n *\r\n * 문제:\r\n * BlockNote의 행/열 추가·삭제(ExtendButton)와 행/열 드래그 재정렬은 내부적으로\r\n * `editor.updateBlock(block, ...)`로 표 블록 전체를 재작성한다. 이때 사용하는 block은\r\n * `nodeToBlock`(contentNodeToTableContent)에서 온 것인데, 이 변환은 고정된 prop 집합\r\n * (colspan/rowspan/backgroundColor/textColor/textAlignment)만 cell.props에 담는다.\r\n * → rowHeight·verticalAlignment 같은 커스텀 글로벌 attribute는 그 시점에 누락되고,\r\n * updateBlock→blockToNode 재작성으로 PM 노드에서도 사라진다(셀 기본값으로 초기화).\r\n *\r\n * 해결:\r\n * appendTransaction에서 \"직전(old) 상태의 셀 attr\"과 \"현재(new) 상태\"를 비교해, 구조\r\n * 편집으로 기본값이 된 셀에 직전 값을 다시 채운다. (row, 행 내 cell) 인덱스로 매칭한다.\r\n *\r\n * - 사용자가 직접 리사이즈/정렬한 경우: 새 값이 non-default라 복원이 끼어들지 않는다.\r\n * - 새로 추가된 행/열의 셀: 직전 값이 없으므로 그대로 기본값(정상).\r\n * - self-terminating: 복원 후 재실행 시 new 값이 채워져 더 복원할 게 없다.\r\n */\r\n\r\nimport { Plugin, PluginKey } from \"prosemirror-state\";\r\nimport type { Node as PMNode } from \"prosemirror-model\";\r\n\r\nexport const tableCellAttrPreserveKey = new PluginKey(\r\n \"lumirTableCellAttrPreserve\",\r\n);\r\n\r\n/** 보존 대상 attr과 \"기본값\"(이 값이면 의미 없음 = 보존/복원 제외). */\r\nconst PRESERVED_ATTRS: { name: string; default: unknown }[] = [\r\n { name: \"rowHeight\", default: null },\r\n { name: \"verticalAlignment\", default: \"top\" },\r\n];\r\n\r\nfunction isMeaningful(attrName: string, value: unknown, def: unknown): boolean {\r\n if (value === null || value === undefined) {\r\n return false;\r\n }\r\n return value !== def;\r\n}\r\n\r\n/** 셀의 보존 대상 attr 중 의미 있는 값만 추린다. 없으면 null. */\r\nfunction meaningfulAttrs(node: PMNode): Record<string, unknown> | null {\r\n let out: Record<string, unknown> | null = null;\r\n for (const { name, default: def } of PRESERVED_ATTRS) {\r\n const v = node.attrs?.[name];\r\n if (isMeaningful(name, v, def)) {\r\n (out ??= {})[name] = v;\r\n }\r\n }\r\n return out;\r\n}\r\n\r\n/**\r\n * 문서에서 table 블록 id → \"row:cell\" → {attr:value} 맵을 만든다.\r\n * id는 blockContainer의 id attr(편집을 가로질러 유지됨)로 매칭한다.\r\n */\r\nfunction collectCellAttrs(\r\n doc: PMNode,\r\n): Map<string, Map<string, Record<string, unknown>>> {\r\n const result = new Map<string, Map<string, Record<string, unknown>>>();\r\n\r\n doc.descendants((node) => {\r\n if (node.type.name !== \"blockContainer\") {\r\n return undefined;\r\n }\r\n const id = node.attrs?.id as string | undefined;\r\n const table = node.firstChild;\r\n if (!id || table?.type.name !== \"table\") {\r\n // 표가 아닌 블록도 children에 표를 가질 수 있으므로 계속 descend.\r\n return undefined;\r\n }\r\n const cells = new Map<string, Record<string, unknown>>();\r\n let rowIndex = 0;\r\n table.forEach((rowNode) => {\r\n if (rowNode.type.name === \"tableRow\") {\r\n let cellIndex = 0;\r\n rowNode.forEach((cellNode) => {\r\n const attrs = meaningfulAttrs(cellNode);\r\n if (attrs) {\r\n cells.set(`${rowIndex}:${cellIndex}`, attrs);\r\n }\r\n cellIndex++;\r\n });\r\n rowIndex++;\r\n }\r\n });\r\n if (cells.size > 0) {\r\n result.set(id, cells);\r\n }\r\n // 표 셀 내부엔 더 깊은 표가 없으므로 하위 순회는 불필요.\r\n return false;\r\n });\r\n\r\n return result;\r\n}\r\n\r\nexport function tableCellAttrPreserve(): Plugin {\r\n return new Plugin({\r\n key: tableCellAttrPreserveKey,\r\n appendTransaction(transactions, oldState, newState) {\r\n if (!transactions.some((tr) => tr.docChanged)) {\r\n return null;\r\n }\r\n const oldMap = collectCellAttrs(oldState.doc);\r\n if (oldMap.size === 0) {\r\n return null;\r\n }\r\n\r\n const tr = newState.tr;\r\n let changed = false;\r\n\r\n // new 문서를 순회하며 구조 편집으로 기본값이 된 셀을 old 값으로 복원.\r\n let curTableCells: Map<string, Record<string, unknown>> | null = null;\r\n let curRowIndex = -1;\r\n let curCellIndex = 0;\r\n\r\n newState.doc.descendants((node, pos) => {\r\n const name = node.type.name;\r\n if (name === \"blockContainer\") {\r\n const id = node.attrs?.id as string | undefined;\r\n const table = node.firstChild;\r\n if (id && table?.type.name === \"table\" && oldMap.has(id)) {\r\n curTableCells = oldMap.get(id)!;\r\n curRowIndex = -1;\r\n } else {\r\n curTableCells = null;\r\n }\r\n return undefined; // 항상 descend(중첩 표 대응)\r\n }\r\n if (!curTableCells) {\r\n return undefined;\r\n }\r\n if (name === \"tableRow\") {\r\n curRowIndex++;\r\n curCellIndex = 0;\r\n return undefined;\r\n }\r\n if (name === \"tableCell\" || name === \"tableHeader\") {\r\n const wanted = curTableCells.get(`${curRowIndex}:${curCellIndex}`);\r\n curCellIndex++;\r\n if (wanted) {\r\n const patch: Record<string, unknown> = {};\r\n let needs = false;\r\n for (const { name: attrName, default: def } of PRESERVED_ATTRS) {\r\n if (!(attrName in wanted)) {\r\n continue;\r\n }\r\n const cur = node.attrs?.[attrName];\r\n // 현재 값이 기본값/빈값일 때만 복원(사용자가 새로 지정한 값은 보존).\r\n if (cur === null || cur === undefined || cur === def) {\r\n patch[attrName] = wanted[attrName];\r\n needs = true;\r\n }\r\n }\r\n if (needs) {\r\n tr.setNodeMarkup(pos, undefined, { ...node.attrs, ...patch });\r\n changed = true;\r\n }\r\n }\r\n return false; // 셀 내부는 순회 불필요\r\n }\r\n return undefined;\r\n });\r\n\r\n return changed ? tr : null;\r\n },\r\n });\r\n}\r\n","import { Extension } from \"@tiptap/core\";\r\nimport { Plugin, PluginKey } from \"prosemirror-state\";\r\nimport { Decoration, DecorationSet } from \"prosemirror-view\";\r\n\r\nexport type TableAlignment = \"left\" | \"center\" | \"right\";\r\n\r\nconst tableAlignmentDecoKey = new PluginKey(\"lumirTableAlignmentDeco\");\r\n\r\n/**\r\n * table 노드의 tableAlignment attr을 읽어 `data-table-alignment`를 노드 DOM에 입히는 데코레이션.\r\n *\r\n * 왜 renderHTML(글로벌 attr)만으로 부족한가: table 노드는 커스텀 nodeView\r\n * (BlockNoteTableView)를 사용하는데, 이 nodeView는 생성 시점의 HTMLAttributes만 wrapper div에\r\n * 적용하고 attr 변경 시 갱신하지 않는다. 그래서 setNodeMarkup으로 attr을 바꿔도 DOM에 반영되지\r\n * 않는다. Decoration.node는 PM이 매 state 변경마다 nodeView의 outer DOM에 속성을 적용하므로\r\n * 정렬 변경이 즉시 화면에 반영된다. (renderHTML attr은 HTML 내보내기/복사용으로 계속 유지.)\r\n */\r\nfunction tableAlignmentDecorationPlugin(): Plugin {\r\n return new Plugin({\r\n key: tableAlignmentDecoKey,\r\n props: {\r\n decorations(state) {\r\n const decorations: Decoration[] = [];\r\n state.doc.descendants((node, pos) => {\r\n if (node.type.name === \"table\") {\r\n const align = node.attrs.tableAlignment;\r\n if (align && align !== \"left\") {\r\n decorations.push(\r\n Decoration.node(pos, pos + node.nodeSize, {\r\n \"data-table-alignment\": align,\r\n }),\r\n );\r\n }\r\n return false; // 표 내부는 순회 불필요\r\n }\r\n return undefined;\r\n });\r\n return DecorationSet.create(state.doc, decorations);\r\n },\r\n },\r\n });\r\n}\r\n\r\n/**\r\n * 표(table) 블록 전체를 에디터 영역 기준으로 좌/가운데/우 정렬하는 Tiptap 확장.\r\n *\r\n * BlockNote 0.35는 테이블 블록 레벨 정렬을 모델링하지 않으므로, verticalAlignment/\r\n * rowHeight와 동일한 패턴으로 `table` 노드에 글로벌 attribute `tableAlignment`를 추가한다.\r\n * - renderHTML이 `data-table-alignment`를 출력(BlockNoteTableView가 HTMLAttributes를\r\n * 적용하는 .bn-block-content[data-content-type=table] div에 붙음) → CSS가 content-폭\r\n * <table>에 margin을 적용해 정렬한다(좌측은 기본이라 attr 미출력).\r\n * - parseHTML로 복사/붙여넣기 라운드트립.\r\n * - 저장(블록 JSON 주입)은 utils/table-vertical-alignment.ts의 injectTableBlockAttrs가 담당.\r\n * - 로드는 BlockNote blockToNode가 block.props를 table 노드 attr로 펼쳐 자동 복원.\r\n */\r\nexport const TableAlignmentExtension = Extension.create({\r\n name: \"tableAlignment\",\r\n\r\n addGlobalAttributes() {\r\n return [\r\n {\r\n types: [\"table\"],\r\n attributes: {\r\n tableAlignment: {\r\n default: \"left\",\r\n parseHTML: (element) =>\r\n element.getAttribute(\"data-table-alignment\") || \"left\",\r\n renderHTML: (attributes) => {\r\n if (\r\n !attributes.tableAlignment ||\r\n attributes.tableAlignment === \"left\"\r\n ) {\r\n return {};\r\n }\r\n return { \"data-table-alignment\": attributes.tableAlignment };\r\n },\r\n },\r\n },\r\n },\r\n ];\r\n },\r\n\r\n addProseMirrorPlugins() {\r\n return [tableAlignmentDecorationPlugin()];\r\n },\r\n});\r\n","import { Extension } from \"@tiptap/core\";\r\nimport { CellSelection, TableMap } from \"prosemirror-tables\";\r\n\r\n/**\r\n * 표 셀에 포커스(커서/선택)가 있을 때 **Ctrl/Cmd+A로 표 전체(모든 셀)를 선택**하는 확장.\r\n *\r\n * 기본 Mod-a는 문서 전체를 선택하지만, 표 안에서는 먼저 표 전체를 선택하는 편이\r\n * 사용성이 좋다(셀 일괄 색/정렬/삭제 등). 동작:\r\n * - 셀 안인데 표 전체가 아직 선택되지 않음 → 표 전체 CellSelection (handled)\r\n * - 이미 표 전체가 선택됨 → false 반환 → 기본 동작(문서 전체 선택)으로 단계적 확장\r\n * - 표 밖 → false (기본 문서 전체 선택)\r\n *\r\n * priority를 높여 기본 selectAll보다 먼저 처리한다.\r\n */\r\nexport const TableSelectAllExtension = Extension.create({\r\n name: \"lumirTableSelectAll\",\r\n priority: 1000,\r\n\r\n addKeyboardShortcuts() {\r\n return {\r\n \"Mod-a\": () => {\r\n const view = this.editor.view;\r\n const { state } = view;\r\n const sel: any = state.selection;\r\n const $from = sel.$from;\r\n\r\n // 선택을 감싸는 table 노드의 depth를 찾는다.\r\n let depth = -1;\r\n for (let d = $from.depth; d > 0; d--) {\r\n if ($from.node(d).type.name === \"table\") {\r\n depth = d;\r\n break;\r\n }\r\n }\r\n if (depth < 0) return false; // 표 밖 → 기본 동작\r\n\r\n const table = $from.node(depth);\r\n const tableStart = $from.start(depth);\r\n const map = TableMap.get(table);\r\n const firstRel = map.map[0]; // 좌상단 셀\r\n const lastRel = map.map[map.map.length - 1]; // 우하단 셀\r\n\r\n // 이미 표 전체가 선택돼 있으면 기본 동작(문서 전체)로 넘긴다.\r\n if (sel instanceof CellSelection) {\r\n const a = sel.$anchorCell.pos - tableStart;\r\n const h = sel.$headCell.pos - tableStart;\r\n if (Math.min(a, h) === firstRel && Math.max(a, h) === lastRel) {\r\n return false;\r\n }\r\n }\r\n\r\n const tr = state.tr.setSelection(\r\n CellSelection.create(\r\n state.doc,\r\n tableStart + firstRel,\r\n tableStart + lastRel,\r\n ),\r\n );\r\n view.dispatch(tr);\r\n return true;\r\n },\r\n };\r\n },\r\n});\r\n","import { TextSelection } from \"prosemirror-state\";\r\n\r\n/**\r\n * 현재 블록 뒤에 2단 컬럼(columnList > column×2 > 빈 단락)을 삽입한다.\r\n *\r\n * BlockNote의 editor.insertBlocks는 public blockSchema에 없는 타입(columnList/column)을\r\n * \"unrecognized type\"으로 거부하므로, ProseMirror 트랜잭션으로 직접 삽입한다.\r\n * id는 코어 UniqueID 확장이 누락 노드에 자동 부여한다(types에 columnList/column 포함).\r\n *\r\n * @returns 삽입 성공 여부.\r\n */\r\nexport function insertTwoColumns(\r\n editor: any,\r\n showDivider: boolean = false,\r\n): boolean {\r\n const tiptap = editor?._tiptapEditor;\r\n if (!tiptap) {\r\n return false;\r\n }\r\n const { state, schema } = tiptap;\r\n const { blockContainer, paragraph, column, columnList } = schema.nodes;\r\n if (!blockContainer || !paragraph || !column || !columnList) {\r\n return false;\r\n }\r\n\r\n const $from = state.selection.$from;\r\n\r\n // 컬럼 중첩 금지: 조상에 column/columnList가 있으면 거부(컬럼 안에 columnList를\r\n // 넣으면 column content \"blockContainer+\" 위반 → 삽입이 throw). 안전하게 no-op.\r\n for (let d = $from.depth; d > 0; d--) {\r\n const name = $from.node(d).type.name;\r\n if (name === \"column\" || name === \"columnList\") {\r\n return false;\r\n }\r\n }\r\n\r\n // 현재 selection을 감싸는 blockContainer를 찾아 그 뒤(blockGroup 형제 위치)에 삽입.\r\n let depth = $from.depth;\r\n while (depth > 0 && $from.node(depth).type.name !== \"blockContainer\") {\r\n depth--;\r\n }\r\n if (depth === 0) {\r\n return false;\r\n }\r\n const insertPos = $from.after(depth);\r\n\r\n const mkBlock = () => blockContainer.create(null, paragraph.create());\r\n const mkColumn = () => column.create(null, mkBlock());\r\n // showDivider는 생성 시 고정(가운데 세로 구분선 유무). id는 UniqueID가 자동 부여.\r\n const list = columnList.create({ showDivider }, [mkColumn(), mkColumn()]);\r\n\r\n // insert/dispatch는 위치가 어떤 이유로든 무효면 throw할 수 있으므로 가드한다.\r\n try {\r\n let tr = state.tr.insert(insertPos, list);\r\n // 첫 컬럼의 단락으로 커서 이동(columnList+1, column+1, blockContainer+1, paragraph+1).\r\n try {\r\n tr = tr.setSelection(TextSelection.create(tr.doc, insertPos + 4));\r\n } catch {\r\n // 위치 매핑 실패 시 커서 이동은 생략(삽입 자체는 유효).\r\n }\r\n tiptap.view.dispatch(tr.scrollIntoView());\r\n return true;\r\n } catch {\r\n return false;\r\n }\r\n}\r\n","\"use client\";\r\n\r\nimport {\r\n BasicTextStyleButton,\r\n BlockTypeSelect,\r\n CreateLinkButton,\r\n FileCaptionButton,\r\n FileDeleteButton,\r\n FileRenameButton,\r\n FileReplaceButton,\r\n FileDownloadButton,\r\n FilePreviewButton,\r\n FormattingToolbar,\r\n NestBlockButton,\r\n UnnestBlockButton,\r\n TableCellMergeButton,\r\n} from \"@blocknote/react\";\r\nimport { TextAlignButtonWithVA } from \"./TextAlignButtonWithVA\";\r\nimport { VerticalAlignButton } from \"./VerticalAlignButton\";\r\nimport { TableAlignButton } from \"./TableAlignButton\";\r\nimport { FontSizeButton } from \"./FontSizeButton\";\r\nimport {\r\n LumirColorStyleButton,\r\n LumirCellColorToolbarButton,\r\n} from \"./color/LumirColorControls\";\r\n\r\n/**\r\n * BlockNote 기본 FormattingToolbar + 세로 정렬 버튼(top/middle/bottom)\r\n *\r\n * @see https://www.blocknotejs.org/examples/ui-components/formatting-toolbar-buttons\r\n */\r\nexport const CustomFormattingToolbar = () => {\r\n return (\r\n <FormattingToolbar>\r\n <BlockTypeSelect key={\"blockTypeSelect\"} />\r\n <TableCellMergeButton key={\"tableCellMergeButton\"} />\r\n\r\n <FileCaptionButton key={\"fileCaptionButton\"} />\r\n <FileReplaceButton key={\"replaceFileButton\"} />\r\n <FileRenameButton key={\"fileRenameButton\"} />\r\n <FileDeleteButton key={\"fileDeleteButton\"} />\r\n <FileDownloadButton key={\"fileDownloadButton\"} />\r\n <FilePreviewButton key={\"filePreviewButton\"} />\r\n\r\n <BasicTextStyleButton basicTextStyle={\"bold\"} key={\"boldStyleButton\"} />\r\n <BasicTextStyleButton\r\n basicTextStyle={\"italic\"}\r\n key={\"italicStyleButton\"}\r\n />\r\n <BasicTextStyleButton\r\n basicTextStyle={\"underline\"}\r\n key={\"underlineStyleButton\"}\r\n />\r\n <BasicTextStyleButton\r\n basicTextStyle={\"strike\"}\r\n key={\"strikeStyleButton\"}\r\n />\r\n\r\n <TextAlignButtonWithVA textAlignment={\"left\"} key={\"textAlignLeftButton\"} />\r\n <TextAlignButtonWithVA\r\n textAlignment={\"center\"}\r\n key={\"textAlignCenterButton\"}\r\n />\r\n <TextAlignButtonWithVA textAlignment={\"right\"} key={\"textAlignRightButton\"} />\r\n\r\n {/* 세로 정렬 버튼 (테이블 셀 선택 시에만 표시) */}\r\n <VerticalAlignButton\r\n verticalAlignment=\"top\"\r\n key={\"verticalAlignTop\"}\r\n />\r\n <VerticalAlignButton\r\n verticalAlignment=\"middle\"\r\n key={\"verticalAlignMiddle\"}\r\n />\r\n <VerticalAlignButton\r\n verticalAlignment=\"bottom\"\r\n key={\"verticalAlignBottom\"}\r\n />\r\n\r\n {/* 표 블록 정렬 버튼 (표 컨텍스트에서만 표시) */}\r\n <TableAlignButton alignment=\"left\" key={\"tableAlignLeft\"} />\r\n <TableAlignButton alignment=\"center\" key={\"tableAlignCenter\"} />\r\n <TableAlignButton alignment=\"right\" key={\"tableAlignRight\"} />\r\n\r\n <FontSizeButton key={\"fontSizeButton\"} />\r\n <LumirColorStyleButton key={\"colorStyleButton\"} />\r\n <LumirCellColorToolbarButton key={\"cellColorButton\"} />\r\n <NestBlockButton key={\"nestBlockButton\"} />\r\n <UnnestBlockButton key={\"unnestBlockButton\"} />\r\n <CreateLinkButton key={\"createLinkButton\"} />\r\n </FormattingToolbar>\r\n );\r\n};\r\n","\"use client\";\r\n\r\n/**\r\n * BlockNote 기본 TextAlignButton의 커스텀 버전.\r\n * 테이블 셀에서 가로 정렬 변경 시 ProseMirror 트랜잭션을 직접 사용하여\r\n * selection이 유지되고 verticalAlignment가 초기화되지 않도록 한다.\r\n */\r\n\r\nimport {\r\n BlockSchema,\r\n checkBlockHasDefaultProp,\r\n checkBlockTypeHasDefaultProp,\r\n InlineContentSchema,\r\n mapTableCell,\r\n StyleSchema,\r\n TableContent,\r\n} from \"@blocknote/core\";\r\nimport { useCallback, useMemo } from \"react\";\r\nimport {\r\n useComponentsContext,\r\n useBlockNoteEditor,\r\n useSelectedBlocks,\r\n} from \"@blocknote/react\";\r\nimport { getSelectedCellPositions } from \"../utils/prosemirror-table-utils\";\r\n\r\ntype TextAlignment = \"left\" | \"center\" | \"right\" | \"justify\";\r\n\r\nconst icons: Record<TextAlignment, JSX.Element> = {\r\n left: (\r\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\" width=\"18\" height=\"18\">\r\n <path d=\"M15 15H3v2h12v-2zm0-8H3v2h12V7zM3 13h18v-2H3v2zm0 8h18v-2H3v2zM3 3v2h18V3H3z\" />\r\n </svg>\r\n ),\r\n center: (\r\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\" width=\"18\" height=\"18\">\r\n <path d=\"M7 15v2h10v-2H7zm-4 6h18v-2H3v2zm0-8h18v-2H3v2zm4-6v2h10V7H7zM3 3v2h18V3H3z\" />\r\n </svg>\r\n ),\r\n right: (\r\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\" width=\"18\" height=\"18\">\r\n <path d=\"M3 21h18v-2H3v2zm6-4h12v-2H9v2zm-6-4h18v-2H3v2zm6-4h12V7H9v2zM3 3v2h18V3H3z\" />\r\n </svg>\r\n ),\r\n justify: (\r\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\" width=\"18\" height=\"18\">\r\n <path d=\"M3 21h18v-2H3v2zm0-4h18v-2H3v2zm0-4h18v-2H3v2zm0-4h18V7H3v2zM3 3v2h18V3H3z\" />\r\n </svg>\r\n ),\r\n};\r\n\r\nconst tooltipMap: Record<TextAlignment, string> = {\r\n left: \"왼쪽 정렬\",\r\n center: \"가운데 정렬\",\r\n right: \"오른쪽 정렬\",\r\n justify: \"양쪽 정렬\",\r\n};\r\n\r\nexport const TextAlignButtonWithVA = (props: {\r\n textAlignment: TextAlignment;\r\n}) => {\r\n const Components = useComponentsContext()!;\r\n const editor = useBlockNoteEditor<\r\n BlockSchema,\r\n InlineContentSchema,\r\n StyleSchema\r\n >();\r\n\r\n const selectedBlocks = useSelectedBlocks(editor);\r\n\r\n const textAlignment = useMemo(() => {\r\n const block = selectedBlocks[0];\r\n\r\n if (checkBlockHasDefaultProp(\"textAlignment\", block, editor)) {\r\n return block.props.textAlignment;\r\n }\r\n if (block.type === \"table\") {\r\n const cellSelection = editor.tableHandles?.getCellSelection();\r\n if (!cellSelection) {\r\n return;\r\n }\r\n const allCellsInTable = cellSelection.cells.map(\r\n ({ row, col }: { row: number; col: number }) =>\r\n mapTableCell(\r\n (block.content as TableContent<any, any>).rows[row].cells[col],\r\n ).props.textAlignment,\r\n );\r\n const firstAlignment = allCellsInTable[0];\r\n\r\n if (allCellsInTable.every((alignment: string) => alignment === firstAlignment)) {\r\n return firstAlignment;\r\n }\r\n }\r\n\r\n return;\r\n }, [editor, selectedBlocks]);\r\n\r\n const setTextAlignment = useCallback(\r\n (newAlignment: TextAlignment) => {\r\n editor.focus();\r\n\r\n for (const block of selectedBlocks) {\r\n if (block.type === \"table\") {\r\n const tiptap = editor._tiptapEditor;\r\n if (!tiptap) continue;\r\n\r\n const positions = getSelectedCellPositions(editor);\r\n if (positions.length === 0) continue;\r\n\r\n const { state } = tiptap;\r\n let tr = state.tr;\r\n\r\n for (const pos of positions) {\r\n const node = tr.doc.nodeAt(pos);\r\n if (node) {\r\n tr = tr.setNodeMarkup(pos, undefined, {\r\n ...node.attrs,\r\n textAlignment: newAlignment,\r\n });\r\n }\r\n }\r\n\r\n tiptap.view?.dispatch(tr);\r\n } else if (checkBlockTypeHasDefaultProp(\"textAlignment\", block.type, editor)) {\r\n editor.updateBlock(block, {\r\n props: { textAlignment: newAlignment },\r\n });\r\n }\r\n }\r\n },\r\n [editor, selectedBlocks],\r\n );\r\n\r\n const show = useMemo(() => {\r\n return !!selectedBlocks.find(\r\n (block) =>\r\n \"textAlignment\" in block.props ||\r\n (block.type === \"table\" && block.children),\r\n );\r\n }, [selectedBlocks]);\r\n\r\n if (!show || !editor.isEditable) {\r\n return null;\r\n }\r\n\r\n return (\r\n <Components.FormattingToolbar.Button\r\n className={\"bn-button\"}\r\n data-test={`alignText${\r\n props.textAlignment.slice(0, 1).toUpperCase() +\r\n props.textAlignment.slice(1)\r\n }`}\r\n onClick={() => setTextAlignment(props.textAlignment)}\r\n isSelected={textAlignment === props.textAlignment}\r\n label={tooltipMap[props.textAlignment]}\r\n mainTooltip={tooltipMap[props.textAlignment]}\r\n icon={icons[props.textAlignment]}\r\n />\r\n );\r\n};\r\n","\"use client\";\r\n\r\nimport { useCallback, useMemo } from \"react\";\r\nimport {\r\n useBlockNoteEditor,\r\n useComponentsContext,\r\n useSelectedBlocks,\r\n} from \"@blocknote/react\";\r\nimport type {\r\n BlockSchema,\r\n InlineContentSchema,\r\n StyleSchema,\r\n} from \"@blocknote/core\";\r\nimport type { VerticalAlignment } from \"../extensions/VerticalAlignmentExtension\";\r\nimport { getSelectedCellPositions } from \"../utils/prosemirror-table-utils\";\r\n\r\nconst icons: Record<VerticalAlignment, JSX.Element> = {\r\n top: (\r\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.5\">\r\n <rect x=\"2\" y=\"2\" width=\"12\" height=\"12\" rx=\"1\" />\r\n <line x1=\"5\" y1=\"5\" x2=\"11\" y2=\"5\" />\r\n </svg>\r\n ),\r\n middle: (\r\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.5\">\r\n <rect x=\"2\" y=\"2\" width=\"12\" height=\"12\" rx=\"1\" />\r\n <line x1=\"5\" y1=\"8\" x2=\"11\" y2=\"8\" />\r\n </svg>\r\n ),\r\n bottom: (\r\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.5\">\r\n <rect x=\"2\" y=\"2\" width=\"12\" height=\"12\" rx=\"1\" />\r\n <line x1=\"5\" y1=\"11\" x2=\"11\" y2=\"11\" />\r\n </svg>\r\n ),\r\n};\r\n\r\nconst tooltips: Record<VerticalAlignment, string> = {\r\n top: \"위쪽 정렬\",\r\n middle: \"세로 가운데 정렬\",\r\n bottom: \"아래쪽 정렬\",\r\n};\r\n\r\nfunction getCurrentVerticalAlignment(editor: any): VerticalAlignment | undefined {\r\n const tiptap = editor._tiptapEditor;\r\n if (!tiptap) return undefined;\r\n\r\n const positions = getSelectedCellPositions(editor);\r\n if (positions.length === 0) return undefined;\r\n\r\n const { state } = tiptap;\r\n const alignments = positions.map((pos) => {\r\n const node = state.doc.nodeAt(pos);\r\n return (node?.attrs?.verticalAlignment as VerticalAlignment) || \"top\";\r\n });\r\n\r\n const first = alignments[0];\r\n return alignments.every((a) => a === first) ? first : undefined;\r\n}\r\n\r\nexport const VerticalAlignButton = (props: {\r\n verticalAlignment: VerticalAlignment;\r\n}) => {\r\n const Components = useComponentsContext()!;\r\n const editor = useBlockNoteEditor<\r\n BlockSchema,\r\n InlineContentSchema,\r\n StyleSchema\r\n >();\r\n\r\n const selectedBlocks = useSelectedBlocks(editor);\r\n\r\n const currentAlignment = useMemo(() => {\r\n return getCurrentVerticalAlignment(editor);\r\n // eslint-disable-next-line react-hooks/exhaustive-deps\r\n }, [editor, selectedBlocks]);\r\n\r\n const setVerticalAlignment = useCallback(\r\n (alignment: VerticalAlignment) => {\r\n const tiptap = editor._tiptapEditor;\r\n if (!tiptap) return;\r\n\r\n const positions = getSelectedCellPositions(editor);\r\n if (positions.length === 0) return;\r\n\r\n editor.focus();\r\n\r\n const { state } = tiptap;\r\n let tr = state.tr;\r\n\r\n for (const pos of positions) {\r\n const node = tr.doc.nodeAt(pos);\r\n if (node) {\r\n tr = tr.setNodeMarkup(pos, undefined, {\r\n ...node.attrs,\r\n verticalAlignment: alignment,\r\n });\r\n }\r\n }\r\n\r\n tiptap.view?.dispatch(tr);\r\n },\r\n [editor],\r\n );\r\n\r\n const isInTable = useMemo(() => {\r\n return selectedBlocks.some((block) => block.type === \"table\");\r\n }, [selectedBlocks]);\r\n\r\n if (!isInTable || !editor.isEditable) {\r\n return null;\r\n }\r\n\r\n return (\r\n <Components.FormattingToolbar.Button\r\n className={\"bn-button\"}\r\n data-test={`verticalAlign${props.verticalAlignment.charAt(0).toUpperCase() + props.verticalAlignment.slice(1)}`}\r\n onClick={() => setVerticalAlignment(props.verticalAlignment)}\r\n isSelected={currentAlignment === props.verticalAlignment}\r\n label={tooltips[props.verticalAlignment]}\r\n mainTooltip={tooltips[props.verticalAlignment]}\r\n icon={icons[props.verticalAlignment]}\r\n />\r\n );\r\n};\r\n","\"use client\";\r\n\r\n/**\r\n * 표(table) 블록 전체를 에디터 영역 기준으로 좌/가운데/우 정렬하는 툴바 버튼.\r\n * 셀 텍스트 정렬(TextAlignButtonWithVA)과 구분되며, 표 블록이 선택된 컨텍스트에서만 노출된다.\r\n * 정렬값은 table 노드 attr(tableAlignment)에 저장한다(setTableAlignment).\r\n */\r\n\r\nimport { useCallback, useMemo } from \"react\";\r\nimport {\r\n useBlockNoteEditor,\r\n useComponentsContext,\r\n useSelectedBlocks,\r\n} from \"@blocknote/react\";\r\nimport type {\r\n BlockSchema,\r\n InlineContentSchema,\r\n StyleSchema,\r\n} from \"@blocknote/core\";\r\nimport type { TableAlignment } from \"../extensions/TableAlignmentExtension\";\r\nimport {\r\n getTableAlignment,\r\n setTableAlignment,\r\n} from \"../utils/prosemirror-table-utils\";\r\n\r\n// 표(직사각형) 안에 정렬 위치를 나타내는 작은 막대를 둔 아이콘.\r\nconst icons: Record<TableAlignment, JSX.Element> = {\r\n left: (\r\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.4\">\r\n <rect x=\"1.5\" y=\"4\" width=\"7\" height=\"8\" rx=\"1\" fill=\"currentColor\" stroke=\"none\" />\r\n <line x1=\"1.5\" y1=\"1.5\" x2=\"14.5\" y2=\"1.5\" />\r\n <line x1=\"1.5\" y1=\"14.5\" x2=\"14.5\" y2=\"14.5\" />\r\n </svg>\r\n ),\r\n center: (\r\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.4\">\r\n <rect x=\"4.5\" y=\"4\" width=\"7\" height=\"8\" rx=\"1\" fill=\"currentColor\" stroke=\"none\" />\r\n <line x1=\"1.5\" y1=\"1.5\" x2=\"14.5\" y2=\"1.5\" />\r\n <line x1=\"1.5\" y1=\"14.5\" x2=\"14.5\" y2=\"14.5\" />\r\n </svg>\r\n ),\r\n right: (\r\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.4\">\r\n <rect x=\"7.5\" y=\"4\" width=\"7\" height=\"8\" rx=\"1\" fill=\"currentColor\" stroke=\"none\" />\r\n <line x1=\"1.5\" y1=\"1.5\" x2=\"14.5\" y2=\"1.5\" />\r\n <line x1=\"1.5\" y1=\"14.5\" x2=\"14.5\" y2=\"14.5\" />\r\n </svg>\r\n ),\r\n};\r\n\r\nconst tooltips: Record<TableAlignment, string> = {\r\n left: \"표 왼쪽 정렬\",\r\n center: \"표 가운데 정렬\",\r\n right: \"표 오른쪽 정렬\",\r\n};\r\n\r\nexport const TableAlignButton = (props: { alignment: TableAlignment }) => {\r\n const Components = useComponentsContext()!;\r\n const editor = useBlockNoteEditor<\r\n BlockSchema,\r\n InlineContentSchema,\r\n StyleSchema\r\n >();\r\n\r\n const selectedBlocks = useSelectedBlocks(editor);\r\n\r\n const tableBlock = useMemo(\r\n () => selectedBlocks.find((block) => block.type === \"table\"),\r\n [selectedBlocks],\r\n );\r\n\r\n const current = useMemo(() => {\r\n if (!tableBlock?.id) return \"left\";\r\n return getTableAlignment(editor, tableBlock.id);\r\n // eslint-disable-next-line react-hooks/exhaustive-deps\r\n }, [editor, tableBlock, selectedBlocks]);\r\n\r\n const apply = useCallback(() => {\r\n if (!tableBlock?.id) return;\r\n editor.focus();\r\n setTableAlignment(editor, tableBlock.id, props.alignment);\r\n }, [editor, tableBlock, props.alignment]);\r\n\r\n if (!tableBlock || !editor.isEditable) {\r\n return null;\r\n }\r\n\r\n return (\r\n <Components.FormattingToolbar.Button\r\n className={\"bn-button\"}\r\n data-test={`tableAlign${props.alignment.charAt(0).toUpperCase() + props.alignment.slice(1)}`}\r\n onClick={apply}\r\n isSelected={current === props.alignment}\r\n label={tooltips[props.alignment]}\r\n mainTooltip={tooltips[props.alignment]}\r\n icon={icons[props.alignment]}\r\n />\r\n );\r\n};\r\n","\"use client\";\r\n\r\n/**\r\n * 포매팅 툴바의 \"글자 크기\" 드롭다운 버튼.\r\n * LumirColorStyleButton(color/LumirColorControls.tsx)과 동일한 패턴:\r\n * Generic.Menu 드롭다운 + addStyles/removeStyles + 다음 틱 focus 복원.\r\n *\r\n * 드롭다운 상단에 −/+ 스테퍼 + 직접 입력(1px 단위, 8~96px clamp)을 두고,\r\n * 그 아래 \"기본\" 리셋 + 프리셋 목록을 빠른 선택용으로 유지한다.\r\n */\r\n\r\nimport {\r\n type BlockSchema,\r\n type InlineContentSchema,\r\n type StyleSchema,\r\n} from \"@blocknote/core\";\r\nimport {\r\n useBlockNoteEditor,\r\n useComponentsContext,\r\n useEditorContentOrSelectionChange,\r\n useSelectedBlocks,\r\n} from \"@blocknote/react\";\r\nimport { useCallback, useEffect, useMemo, useState } from \"react\";\r\nimport {\r\n FONT_SIZE_MAX,\r\n FONT_SIZE_MIN,\r\n FONT_SIZE_PRESETS,\r\n clampFontSizePx,\r\n parseFontSizePx,\r\n toFontSizeValue,\r\n} from \"../styles/FontSizeStyle\";\r\n\r\nconst DEFAULT_LABEL = \"기본\";\r\n\r\n/** \"18px\" → \"18\" (드롭다운/트리거 표시용) */\r\nconst toLabel = (size: string) => size.replace(/px$/, \"\");\r\n\r\nfunction FontSizeIcon({ size }: { size: string }) {\r\n return (\r\n <span\r\n style={{\r\n pointerEvents: \"none\",\r\n fontSize: \"12px\",\r\n fontWeight: 600,\r\n lineHeight: \"20px\",\r\n whiteSpace: \"nowrap\",\r\n }}\r\n >\r\n {size ? toLabel(size) : \"가A\"}\r\n </span>\r\n );\r\n}\r\n\r\nexport function FontSizeButton() {\r\n const Components = useComponentsContext()!;\r\n const editor = useBlockNoteEditor<\r\n BlockSchema,\r\n InlineContentSchema,\r\n StyleSchema\r\n >();\r\n\r\n // 커스텀 스타일은 제네릭 StyleSchema에서 추론되지 않으므로 느슨한 타입으로 처리\r\n // (LumirColorStyleButton과 동일한 관례)\r\n const ed = editor as unknown as {\r\n getActiveStyles: () => Record<string, string>;\r\n addStyles: (styles: Record<string, string>) => void;\r\n removeStyles: (styles: Record<string, string>) => void;\r\n };\r\n\r\n const styleSchema = editor.schema.styleSchema as Record<\r\n string,\r\n { type?: string; propSchema?: string }\r\n >;\r\n const fontSizeInSchema =\r\n styleSchema.fontSize?.type === \"fontSize\" &&\r\n styleSchema.fontSize?.propSchema === \"string\";\r\n\r\n const selectedBlocks = useSelectedBlocks(editor);\r\n\r\n const [currentSize, setCurrentSize] = useState<string>(\r\n fontSizeInSchema ? ed.getActiveStyles().fontSize || \"\" : \"\",\r\n );\r\n\r\n useEditorContentOrSelectionChange(() => {\r\n if (fontSizeInSchema) {\r\n setCurrentSize(ed.getActiveStyles().fontSize || \"\");\r\n }\r\n }, editor);\r\n\r\n // 명시 크기가 없으면(\"기본\") 14px를 기준으로 증감/표시한다.\r\n const currentPx = parseFontSizePx(currentSize);\r\n\r\n // 직접 입력 필드의 로컬 상태 (선택/적용으로 실제 크기가 바뀌면 동기화)\r\n const [inputValue, setInputValue] = useState<string>(String(currentPx));\r\n useEffect(() => {\r\n setInputValue(String(currentPx));\r\n }, [currentPx]);\r\n\r\n const setFontSize = useCallback(\r\n (size: string) => {\r\n size === \"\"\r\n ? ed.removeStyles({ fontSize: \"\" })\r\n : ed.addStyles({ fontSize: size });\r\n // Mantine Toolbar의 useFocusTrap 호환을 위해 다음 틱에 focus.\r\n setTimeout(() => editor.focus());\r\n },\r\n // eslint-disable-next-line react-hooks/exhaustive-deps\r\n [editor],\r\n );\r\n\r\n // −/+ 스테퍼: 메뉴를 닫지 않고 선택 영역을 유지한 채 1px씩 적용한다.\r\n const stepBy = useCallback(\r\n (delta: number) => {\r\n ed.addStyles({ fontSize: toFontSizeValue(currentPx + delta) });\r\n },\r\n // eslint-disable-next-line react-hooks/exhaustive-deps\r\n [currentPx],\r\n );\r\n\r\n // 직접 입력 적용: 유효하면 clamp 후 적용, 무효/빈값이면 표시값 복원.\r\n const applyInput = useCallback(() => {\r\n const n = parseInt(inputValue, 10);\r\n if (Number.isFinite(n)) {\r\n ed.addStyles({ fontSize: toFontSizeValue(n) });\r\n } else {\r\n setInputValue(String(currentPx));\r\n }\r\n // eslint-disable-next-line react-hooks/exhaustive-deps\r\n }, [inputValue, currentPx]);\r\n\r\n const show = useMemo(() => {\r\n if (!fontSizeInSchema) {\r\n return false;\r\n }\r\n for (const block of selectedBlocks) {\r\n if (block.content !== undefined) {\r\n return true;\r\n }\r\n }\r\n return false;\r\n }, [fontSizeInSchema, selectedBlocks]);\r\n\r\n if (!show || !editor.isEditable) {\r\n return null;\r\n }\r\n\r\n const tooltip = \"글자 크기\";\r\n const preventBlur = (e: { preventDefault: () => void }) => e.preventDefault();\r\n\r\n return (\r\n <Components.Generic.Menu.Root>\r\n <Components.Generic.Menu.Trigger>\r\n <Components.FormattingToolbar.Button\r\n className={\"bn-button\"}\r\n data-test=\"font-size\"\r\n label={tooltip}\r\n mainTooltip={tooltip}\r\n icon={<FontSizeIcon size={currentSize} />}\r\n />\r\n </Components.Generic.Menu.Trigger>\r\n <Components.Generic.Menu.Dropdown className={\"bn-menu-dropdown\"}>\r\n <Components.Generic.Menu.Label>글자 크기</Components.Generic.Menu.Label>\r\n {/* 1px 스테퍼 + 직접 입력 (Menu.Item이 아니므로 클릭해도 메뉴가 닫히지 않음) */}\r\n <div className=\"lumir-fs-stepper\">\r\n <button\r\n type=\"button\"\r\n className=\"lumir-fs-stepper-btn\"\r\n aria-label=\"글자 크기 1px 줄이기\"\r\n data-test=\"font-size-decrease\"\r\n disabled={currentPx <= FONT_SIZE_MIN}\r\n onMouseDown={preventBlur}\r\n onClick={() => stepBy(-1)}\r\n >\r\n −\r\n </button>\r\n <input\r\n className=\"lumir-fs-stepper-input\"\r\n type=\"text\"\r\n inputMode=\"numeric\"\r\n aria-label=\"글자 크기 입력\"\r\n data-test=\"font-size-input\"\r\n value={inputValue}\r\n onChange={(e) =>\r\n setInputValue(e.target.value.replace(/[^0-9]/g, \"\"))\r\n }\r\n onClick={(e) => e.stopPropagation()}\r\n onKeyDown={(e) => {\r\n e.stopPropagation();\r\n if (e.key === \"Enter\") {\r\n e.preventDefault();\r\n applyInput();\r\n } else if (e.key === \"ArrowUp\") {\r\n e.preventDefault();\r\n stepBy(1);\r\n } else if (e.key === \"ArrowDown\") {\r\n e.preventDefault();\r\n stepBy(-1);\r\n }\r\n }}\r\n onBlur={applyInput}\r\n />\r\n <button\r\n type=\"button\"\r\n className=\"lumir-fs-stepper-btn\"\r\n aria-label=\"글자 크기 1px 늘리기\"\r\n data-test=\"font-size-increase\"\r\n disabled={currentPx >= FONT_SIZE_MAX}\r\n onMouseDown={preventBlur}\r\n onClick={() => stepBy(1)}\r\n >\r\n +\r\n </button>\r\n </div>\r\n <Components.Generic.Menu.Item\r\n onClick={() => setFontSize(\"\")}\r\n checked={currentSize === \"\"}\r\n data-test={\"font-size-default\"}\r\n key={\"font-size-default\"}\r\n >\r\n {DEFAULT_LABEL}\r\n </Components.Generic.Menu.Item>\r\n {FONT_SIZE_PRESETS.map((size) => (\r\n <Components.Generic.Menu.Item\r\n onClick={() => setFontSize(size)}\r\n checked={currentSize === size}\r\n data-test={\"font-size-\" + toLabel(size)}\r\n key={\"font-size-\" + size}\r\n >\r\n {toLabel(size)}\r\n </Components.Generic.Menu.Item>\r\n ))}\r\n </Components.Generic.Menu.Dropdown>\r\n </Components.Generic.Menu.Root>\r\n );\r\n}\r\n","\"use client\";\r\n\r\n/**\r\n * 색상 컨트롤(텍스트 색/배경) — 컨텍스트별로 그룹 제목·툴팁을 명확히 구분한다.\r\n *\r\n * 배경:\r\n * - BlockNote의 공유 `ColorPicker`는 그룹 제목을 editor 전역 dictionary\r\n * (`color_picker.text_title`/`background_title`)에서 읽으므로, \"글(텍스트) 배경\"과\r\n * \"셀 배경\"을 같은 \"텍스트/배경\" 라벨로 표기해 어떤 배경을 바꾸는 버튼인지 모호했다.\r\n * - 공유 `ColorPicker`/`ColorIcon`은 패키지 루트로 export되지 않아 dictionary를\r\n * 덮어쓸 수도, 직접 import할 수도 없다(dictionary는 editor 인스턴스에 묶임).\r\n * → 그래서 `ColorPicker`를 제목 prop을 받도록 복제(`LumirColorPicker`)하고,\r\n * 툴바(인라인)·셀 메뉴용 버튼을 각각 만들어 컨텍스트별 라벨을 주입한다.\r\n *\r\n * 라벨 규칙:\r\n * - 툴바(인라인 글자 색/배경): 툴팁 \"텍스트 색·배경\", 그룹 \"텍스트 색\" / \"텍스트 배경\"\r\n * - 셀 메뉴(셀 글자 색/배경): 트리거 \"셀 색·배경\", 그룹 \"셀 글자색\" / \"셀 배경\"\r\n * 버튼은 색·배경을 모두 바꾸므로 툴팁을 \"배경\"만으로 적으면 색 기능이 가려지고\r\n * (특히 셀 메뉴의 텍스트 색), 그룹 제목에 \"텍스트 배경\"/\"셀 배경\"을 명시해 두 배경\r\n * 기능을 분명히 구분한다.\r\n */\r\n\r\nimport {\r\n type BlockSchema,\r\n type DefaultBlockSchema,\r\n type DefaultInlineContentSchema,\r\n type DefaultStyleSchema,\r\n type InlineContentSchema,\r\n isTableCell,\r\n mapTableCell,\r\n type StyleSchema,\r\n} from \"@blocknote/core\";\r\nimport {\r\n SplitButton,\r\n type TableCellMenuProps,\r\n useBlockNoteEditor,\r\n useComponentsContext,\r\n useDictionary,\r\n useEditorContentOrSelectionChange,\r\n useSelectedBlocks,\r\n} from \"@blocknote/react\";\r\nimport { type ReactNode, useCallback, useMemo, useRef, useState } from \"react\";\r\nimport {\r\n getSelectedCellPositions,\r\n setCellAttrAtPositions,\r\n} from \"../../utils/prosemirror-table-utils\";\r\n\r\nconst COLORS = [\r\n \"default\",\r\n \"gray\",\r\n \"brown\",\r\n \"red\",\r\n \"orange\",\r\n \"yellow\",\r\n \"green\",\r\n \"blue\",\r\n \"purple\",\r\n \"pink\",\r\n] as const;\r\n\r\n/** BlockNote 내부 ColorIcon이 export되지 않아 동일 구현(색 스와치 \"A\")을 복제. */\r\nfunction ColorIcon(props: {\r\n textColor?: string;\r\n backgroundColor?: string;\r\n size?: number;\r\n}) {\r\n const textColor = props.textColor || \"default\";\r\n const backgroundColor = props.backgroundColor || \"default\";\r\n const size = props.size || 16;\r\n return (\r\n <div\r\n className={\"bn-color-icon\"}\r\n data-background-color={backgroundColor}\r\n data-text-color={textColor}\r\n style={{\r\n pointerEvents: \"none\",\r\n fontSize: `${size * 0.75}px`,\r\n height: `${size}px`,\r\n lineHeight: `${size}px`,\r\n textAlign: \"center\",\r\n width: `${size}px`,\r\n }}\r\n >\r\n A\r\n </div>\r\n );\r\n}\r\n\r\n/** 셀 채움(배경) 아이콘 — 인라인 \"A\" 아이콘과 구분되는 페인트통(fill) 모양. */\r\nfunction CellFillIcon({ size = 18 }: { size?: number }) {\r\n return (\r\n <svg\r\n width={size}\r\n height={size}\r\n viewBox=\"0 0 24 24\"\r\n fill=\"currentColor\"\r\n style={{ pointerEvents: \"none\" }}\r\n aria-hidden=\"true\"\r\n >\r\n <path d=\"M16.56 8.94 7.62 0 6.21 1.41l2.38 2.38-5.15 5.15c-.59.59-.59 1.54 0 2.12l5.5 5.5c.29.29.68.44 1.06.44s.77-.15 1.06-.44l5.5-5.5c.59-.58.59-1.53 0-2.12zM5.21 10 10 5.21 14.79 10H5.21zM19 11.5s-2 2.17-2 3.5c0 1.1.9 2 2 2s2-.9 2-2c0-1.33-2-3.5-2-3.5z\" />\r\n </svg>\r\n );\r\n}\r\n\r\n/**\r\n * 공유 ColorPicker의 복제판. 그룹 제목을 `textTitle`/`backgroundTitle`로 주입할 수\r\n * 있다(미지정 시 dictionary 기본값). data-test는 원본과 동일하게 유지.\r\n */\r\nfunction LumirColorPicker(props: {\r\n onClick?: () => void;\r\n iconSize?: number;\r\n textTitle?: string;\r\n backgroundTitle?: string;\r\n text?: { color: string; setColor: (color: string) => void };\r\n background?: { color: string; setColor: (color: string) => void };\r\n}) {\r\n const Components = useComponentsContext()!;\r\n const dict = useDictionary();\r\n\r\n return (\r\n <>\r\n {props.text ? (\r\n <>\r\n <Components.Generic.Menu.Label>\r\n {props.textTitle ?? dict.color_picker.text_title}\r\n </Components.Generic.Menu.Label>\r\n {COLORS.map((color) => (\r\n <Components.Generic.Menu.Item\r\n onClick={() => {\r\n props.onClick?.();\r\n props.text!.setColor(color);\r\n }}\r\n data-test={\"text-color-\" + color}\r\n icon={<ColorIcon textColor={color} size={props.iconSize} />}\r\n checked={props.text!.color === color}\r\n key={\"text-color-\" + color}\r\n >\r\n {dict.color_picker.colors[color]}\r\n </Components.Generic.Menu.Item>\r\n ))}\r\n </>\r\n ) : null}\r\n {props.background ? (\r\n <>\r\n <Components.Generic.Menu.Label>\r\n {props.backgroundTitle ?? dict.color_picker.background_title}\r\n </Components.Generic.Menu.Label>\r\n {COLORS.map((color) => (\r\n <Components.Generic.Menu.Item\r\n onClick={() => {\r\n props.onClick?.();\r\n props.background!.setColor(color);\r\n }}\r\n data-test={\"background-color-\" + color}\r\n icon={\r\n <ColorIcon backgroundColor={color} size={props.iconSize} />\r\n }\r\n checked={props.background!.color === color}\r\n key={\"background-color-\" + color}\r\n >\r\n {dict.color_picker.colors[color]}\r\n </Components.Generic.Menu.Item>\r\n ))}\r\n </>\r\n ) : null}\r\n </>\r\n );\r\n}\r\n\r\n/**\r\n * 툴바(인라인 텍스트) 색상 버튼. 글자 색 + 글자 배경을 바꾼다.\r\n * BlockNote ColorStyleButton의 복제판이며 그룹 제목/툴팁만 \"텍스트\"로 명시한다.\r\n */\r\nexport function LumirColorStyleButton() {\r\n const Components = useComponentsContext()!;\r\n const editor = useBlockNoteEditor<\r\n BlockSchema,\r\n InlineContentSchema,\r\n StyleSchema\r\n >();\r\n\r\n // 기본 스타일 스키마(textColor/backgroundColor)는 제네릭 StyleSchema에서 모두\r\n // undefined로 추론되므로, 스타일 읽기/쓰기는 느슨한 타입 별칭으로 처리한다.\r\n const ed = editor as unknown as {\r\n getActiveStyles: () => Record<string, string>;\r\n addStyles: (styles: Record<string, string>) => void;\r\n removeStyles: (styles: Record<string, string>) => void;\r\n };\r\n\r\n const styleSchema = editor.schema.styleSchema as Record<\r\n string,\r\n { type?: string; propSchema?: string }\r\n >;\r\n const textColorInSchema =\r\n styleSchema.textColor?.type === \"textColor\" &&\r\n styleSchema.textColor?.propSchema === \"string\";\r\n const backgroundColorInSchema =\r\n styleSchema.backgroundColor?.type === \"backgroundColor\" &&\r\n styleSchema.backgroundColor?.propSchema === \"string\";\r\n\r\n const selectedBlocks = useSelectedBlocks(editor);\r\n\r\n const [currentTextColor, setCurrentTextColor] = useState<string>(\r\n textColorInSchema ? ed.getActiveStyles().textColor || \"default\" : \"default\",\r\n );\r\n const [currentBackgroundColor, setCurrentBackgroundColor] = useState<string>(\r\n backgroundColorInSchema\r\n ? ed.getActiveStyles().backgroundColor || \"default\"\r\n : \"default\",\r\n );\r\n\r\n useEditorContentOrSelectionChange(() => {\r\n const active = ed.getActiveStyles();\r\n if (textColorInSchema) {\r\n setCurrentTextColor(active.textColor || \"default\");\r\n }\r\n if (backgroundColorInSchema) {\r\n setCurrentBackgroundColor(active.backgroundColor || \"default\");\r\n }\r\n }, editor);\r\n\r\n const setTextColor = useCallback(\r\n (color: string) => {\r\n color === \"default\"\r\n ? ed.removeStyles({ textColor: color })\r\n : ed.addStyles({ textColor: color });\r\n // Mantine Toolbar의 useFocusTrap 호환을 위해 다음 틱에 focus.\r\n setTimeout(() => editor.focus());\r\n },\r\n // eslint-disable-next-line react-hooks/exhaustive-deps\r\n [editor],\r\n );\r\n\r\n const setBackgroundColor = useCallback(\r\n (color: string) => {\r\n color === \"default\"\r\n ? ed.removeStyles({ backgroundColor: color })\r\n : ed.addStyles({ backgroundColor: color });\r\n setTimeout(() => editor.focus());\r\n },\r\n // eslint-disable-next-line react-hooks/exhaustive-deps\r\n [editor],\r\n );\r\n\r\n const show = useMemo(() => {\r\n if (!textColorInSchema && !backgroundColorInSchema) {\r\n return false;\r\n }\r\n for (const block of selectedBlocks) {\r\n if (block.content !== undefined) {\r\n return true;\r\n }\r\n }\r\n return false;\r\n }, [backgroundColorInSchema, selectedBlocks, textColorInSchema]);\r\n\r\n if (!show || !editor.isEditable) {\r\n return null;\r\n }\r\n\r\n const tooltip = \"텍스트 색·배경\";\r\n\r\n return (\r\n <Components.Generic.Menu.Root>\r\n <Components.Generic.Menu.Trigger>\r\n <Components.FormattingToolbar.Button\r\n className={\"bn-button\"}\r\n data-test=\"colors\"\r\n label={tooltip}\r\n mainTooltip={tooltip}\r\n icon={\r\n <ColorIcon\r\n textColor={currentTextColor}\r\n backgroundColor={currentBackgroundColor}\r\n size={20}\r\n />\r\n }\r\n />\r\n </Components.Generic.Menu.Trigger>\r\n <Components.Generic.Menu.Dropdown\r\n className={\"bn-menu-dropdown bn-color-picker-dropdown\"}\r\n >\r\n <LumirColorPicker\r\n textTitle=\"텍스트 색\"\r\n backgroundTitle=\"텍스트 배경\"\r\n text={\r\n textColorInSchema\r\n ? { color: currentTextColor, setColor: setTextColor }\r\n : undefined\r\n }\r\n background={\r\n backgroundColorInSchema\r\n ? { color: currentBackgroundColor, setColor: setBackgroundColor }\r\n : undefined\r\n }\r\n />\r\n </Components.Generic.Menu.Dropdown>\r\n </Components.Generic.Menu.Root>\r\n );\r\n}\r\n\r\n/**\r\n * 포매팅 툴바의 \"셀 배경색\" 버튼 — 여러 셀을 드래그(DND)로 선택했을 때 선택된\r\n * 모든 셀의 **배경색**을 일괄 변경한다(병합 버튼과 동일하게 다중 셀 선택에서만\r\n * 노출). 단일 셀은 셀 우측 그립 메뉴가 담당하므로 여기서는 숨긴다.\r\n *\r\n * 적용은 `setNodeMarkup`(셀 노드 attr) 방식이라 **셀 선택이 유지**된다 → 연속\r\n * 재색칠 가능(Notion 동작). updateBlock과 달리 내용 전체를 교체하지 않는다.\r\n *\r\n * 주의: 색 스와치 클릭은 드롭다운 상호작용이므로, 메뉴가 열리는 순간 셀 위치를\r\n * 스냅샷해 둔다. 적용 시 라이브 선택을 우선 쓰되(선택 유지), 혹시 붕괴했으면\r\n * 스냅샷으로 폴백해 색은 정확히 적용한다.\r\n *\r\n * 한계: 병합 셀(colspan/rowspan)은 이번 범위 외(예외만 방지).\r\n */\r\nexport function LumirCellColorToolbarButton() {\r\n const Components = useComponentsContext()!;\r\n const editor = useBlockNoteEditor<\r\n { table: DefaultBlockSchema[\"table\"] },\r\n InlineContentSchema,\r\n StyleSchema\r\n >();\r\n const selectedBlocks = useSelectedBlocks(editor);\r\n\r\n // 다중 셀 선택 여부(병합 버튼과 동일 패턴: selectedBlocks 변화 시 재계산).\r\n const isMultiCell = useMemo(() => {\r\n if (selectedBlocks.length !== 1 || selectedBlocks[0].type !== \"table\") {\r\n return false;\r\n }\r\n const cs = editor.tableHandles?.getCellSelection();\r\n return !!cs && cs.cells.length > 1;\r\n }, [editor, selectedBlocks]);\r\n\r\n // 메뉴 열림 시점의 셀 위치(pos) 스냅샷.\r\n const stashRef = useRef<number[]>([]);\r\n\r\n const applyBackground = useCallback(\r\n (color: string) => {\r\n // 라이브 선택 우선(선택 유지), 비어 있으면 스냅샷 폴백(색은 정확히 적용).\r\n const live = getSelectedCellPositions(editor);\r\n const positions = live.length > 0 ? live : stashRef.current;\r\n setCellAttrAtPositions(editor, positions, \"backgroundColor\", color);\r\n // 셀 선택이 다시 보이도록 에디터로 포커스 복귀.\r\n setTimeout(() => editor.focus());\r\n },\r\n [editor],\r\n );\r\n\r\n if (!editor.isEditable || !isMultiCell) {\r\n return null;\r\n }\r\n\r\n const tooltip = \"셀 배경색\";\r\n\r\n return (\r\n <Components.Generic.Menu.Root\r\n onOpenChange={(open: boolean) => {\r\n // 드롭다운이 열리는 순간 셀 위치 스냅샷(상호작용 중 선택 붕괴 대비).\r\n if (open) {\r\n stashRef.current = getSelectedCellPositions(editor);\r\n }\r\n }}\r\n >\r\n <Components.Generic.Menu.Trigger>\r\n <Components.FormattingToolbar.Button\r\n className={\"bn-button\"}\r\n data-test=\"cell-colors\"\r\n label={tooltip}\r\n mainTooltip={tooltip}\r\n icon={<CellFillIcon size={18} />}\r\n />\r\n </Components.Generic.Menu.Trigger>\r\n <Components.Generic.Menu.Dropdown\r\n className={\"bn-menu-dropdown bn-color-picker-dropdown\"}\r\n >\r\n {/* 일괄 변경은 배경색만 제공(글자색 그룹 제거). */}\r\n <LumirColorPicker\r\n backgroundTitle=\"셀 배경\"\r\n background={{ color: \"default\", setColor: applyBackground }}\r\n />\r\n </Components.Generic.Menu.Dropdown>\r\n </Components.Generic.Menu.Root>\r\n );\r\n}\r\n\r\n/**\r\n * 셀 메뉴의 색상 서브메뉴. 셀 글자 색 + 셀 배경을 바꾼다.\r\n * BlockNote 셀 ColorPickerButton의 복제판이며 트리거/그룹 제목만 \"셀\"로 명시한다.\r\n */\r\nfunction LumirCellColorPickerButton<\r\n I extends InlineContentSchema = DefaultInlineContentSchema,\r\n S extends StyleSchema = DefaultStyleSchema,\r\n>(props: TableCellMenuProps<I, S>) {\r\n const Components = useComponentsContext()!;\r\n const editor = useBlockNoteEditor<\r\n { table: DefaultBlockSchema[\"table\"] },\r\n I,\r\n S\r\n >();\r\n\r\n const updateColor = (color: string, type: \"text\" | \"background\") => {\r\n const newTable = props.block.content.rows.map((row) => ({\r\n ...row,\r\n cells: row.cells.map((cell) => mapTableCell(cell)),\r\n }));\r\n\r\n if (type === \"text\") {\r\n newTable[props.rowIndex].cells[props.colIndex].props.textColor = color;\r\n } else {\r\n newTable[props.rowIndex].cells[props.colIndex].props.backgroundColor =\r\n color;\r\n }\r\n\r\n editor.updateBlock(props.block, {\r\n type: \"table\",\r\n content: { ...props.block.content, rows: newTable },\r\n });\r\n // updateBlock이 선택을 블록 밖으로 옮기므로 커서를 블록으로 되돌린다.\r\n editor.setTextCursorPosition(props.block);\r\n };\r\n\r\n const currentCell =\r\n props.block.content.rows[props.rowIndex]?.cells?.[props.colIndex];\r\n\r\n if (\r\n !currentCell ||\r\n (editor.settings.tables.cellTextColor === false &&\r\n editor.settings.tables.cellBackgroundColor === false)\r\n ) {\r\n return null;\r\n }\r\n\r\n return (\r\n <Components.Generic.Menu.Root position={\"right\"} sub={true}>\r\n <Components.Generic.Menu.Trigger sub={true}>\r\n <Components.Generic.Menu.Item className={\"bn-menu-item\"} subTrigger={true}>\r\n 셀 색·배경\r\n </Components.Generic.Menu.Item>\r\n </Components.Generic.Menu.Trigger>\r\n\r\n <Components.Generic.Menu.Dropdown\r\n sub={true}\r\n className={\"bn-menu-dropdown bn-color-picker-dropdown\"}\r\n >\r\n <LumirColorPicker\r\n iconSize={18}\r\n textTitle=\"셀 글자색\"\r\n backgroundTitle=\"셀 배경\"\r\n text={\r\n editor.settings.tables.cellTextColor\r\n ? {\r\n color: isTableCell(currentCell)\r\n ? currentCell.props.textColor\r\n : \"default\",\r\n setColor: (color) => updateColor(color, \"text\"),\r\n }\r\n : undefined\r\n }\r\n background={\r\n editor.settings.tables.cellBackgroundColor\r\n ? {\r\n color: isTableCell(currentCell)\r\n ? currentCell.props.backgroundColor\r\n : \"default\",\r\n setColor: (color) => updateColor(color, \"background\"),\r\n }\r\n : undefined\r\n }\r\n />\r\n </Components.Generic.Menu.Dropdown>\r\n </Components.Generic.Menu.Root>\r\n );\r\n}\r\n\r\n/**\r\n * 셀 핸들 드롭다운 메뉴. 기본 TableCellMenu 복제판으로, 색상 버튼만 \"셀\" 라벨이\r\n * 명시된 LumirCellColorPickerButton으로 교체한다(병합 버튼은 그대로).\r\n * TableCellButton의 `tableCellMenu` prop으로 주입한다.\r\n */\r\nexport function LumirTableCellMenu<\r\n I extends InlineContentSchema = DefaultInlineContentSchema,\r\n S extends StyleSchema = DefaultStyleSchema,\r\n>(props: TableCellMenuProps<I, S> & { children?: ReactNode }) {\r\n const Components = useComponentsContext()!;\r\n\r\n return (\r\n <Components.Generic.Menu.Dropdown\r\n className={\"bn-menu-dropdown bn-drag-handle-menu\"}\r\n >\r\n {props.children || (\r\n <>\r\n <SplitButton\r\n block={props.block}\r\n rowIndex={props.rowIndex}\r\n colIndex={props.colIndex}\r\n />\r\n <LumirCellColorPickerButton\r\n block={props.block}\r\n rowIndex={props.rowIndex}\r\n colIndex={props.colIndex}\r\n />\r\n </>\r\n )}\r\n </Components.Generic.Menu.Dropdown>\r\n );\r\n}\r\n","\"use client\";\r\n\r\n/**\r\n * 블록 드래그 핸들(좌측 ⠿) 드롭다운 메뉴의 커스텀 버전.\r\n *\r\n * BlockNote 기본 DragHandleMenu(삭제/색/행 제목/열 제목)를 그대로 재현하고,\r\n * **표 블록일 때 \"표 정렬\"(좌/가운데/우) 항목을 추가**한다.\r\n * (표 정렬을 셀 grip 메뉴가 아니라 블록 메뉴로 노출하기 위함.)\r\n *\r\n * 기본 DragHandleMenu는 children을 주면 기본 항목 대신 children만 렌더하므로,\r\n * 기본 항목들을 명시적으로 재구성한 뒤 정렬 항목을 덧붙인다.\r\n * (TableRowHeaderItem/TableColumnHeaderItem은 비-표 블록에서 자동으로 null을 반환한다.)\r\n */\r\n\r\nimport {\r\n DragHandleMenu,\r\n RemoveBlockItem,\r\n BlockColorsItem,\r\n TableRowHeaderItem,\r\n TableColumnHeaderItem,\r\n useComponentsContext,\r\n useBlockNoteEditor,\r\n useDictionary,\r\n type DragHandleMenuProps,\r\n} from \"@blocknote/react\";\r\nimport type { TableAlignment } from \"../extensions/TableAlignmentExtension\";\r\nimport {\r\n getTableAlignment,\r\n setTableAlignment,\r\n} from \"../utils/prosemirror-table-utils\";\r\n\r\nconst TABLE_ALIGN_ICONS: Record<TableAlignment, JSX.Element> = {\r\n left: (\r\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.4\">\r\n <rect x=\"1.5\" y=\"4\" width=\"7\" height=\"8\" rx=\"1\" fill=\"currentColor\" stroke=\"none\" />\r\n <line x1=\"1.5\" y1=\"1.5\" x2=\"14.5\" y2=\"1.5\" />\r\n <line x1=\"1.5\" y1=\"14.5\" x2=\"14.5\" y2=\"14.5\" />\r\n </svg>\r\n ),\r\n center: (\r\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.4\">\r\n <rect x=\"4.5\" y=\"4\" width=\"7\" height=\"8\" rx=\"1\" fill=\"currentColor\" stroke=\"none\" />\r\n <line x1=\"1.5\" y1=\"1.5\" x2=\"14.5\" y2=\"1.5\" />\r\n <line x1=\"1.5\" y1=\"14.5\" x2=\"14.5\" y2=\"14.5\" />\r\n </svg>\r\n ),\r\n right: (\r\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.4\">\r\n <rect x=\"7.5\" y=\"4\" width=\"7\" height=\"8\" rx=\"1\" fill=\"currentColor\" stroke=\"none\" />\r\n <line x1=\"1.5\" y1=\"1.5\" x2=\"14.5\" y2=\"1.5\" />\r\n <line x1=\"1.5\" y1=\"14.5\" x2=\"14.5\" y2=\"14.5\" />\r\n </svg>\r\n ),\r\n};\r\n\r\nconst TABLE_ALIGN_LABELS: Record<TableAlignment, string> = {\r\n left: \"표 왼쪽 정렬\",\r\n center: \"표 가운데 정렬\",\r\n right: \"표 오른쪽 정렬\",\r\n};\r\n\r\n/** 표 블록일 때만 \"표 정렬\" 항목(좌/가운데/우)을 렌더. table 노드 attr(tableAlignment)에 적용. */\r\nfunction TableAlignmentItems(props: { block: any }) {\r\n const Components = useComponentsContext()!;\r\n const editor = useBlockNoteEditor();\r\n\r\n if (props.block?.type !== \"table\" || !props.block?.id) {\r\n return null;\r\n }\r\n\r\n const current = getTableAlignment(editor, props.block.id) as TableAlignment;\r\n\r\n return (\r\n <>\r\n {([\"left\", \"center\", \"right\"] as TableAlignment[]).map((align) => (\r\n <Components.Generic.Menu.Item\r\n key={`tableAlign-${align}`}\r\n icon={TABLE_ALIGN_ICONS[align]}\r\n checked={current === align}\r\n onClick={() => {\r\n setTableAlignment(editor, props.block.id, align);\r\n editor.setTextCursorPosition(props.block.id);\r\n }}\r\n >\r\n {TABLE_ALIGN_LABELS[align]}\r\n </Components.Generic.Menu.Item>\r\n ))}\r\n </>\r\n );\r\n}\r\n\r\nexport const LumirDragHandleMenu = (props: DragHandleMenuProps) => {\r\n const dict = useDictionary();\r\n\r\n return (\r\n <DragHandleMenu {...props}>\r\n <RemoveBlockItem {...props}>\r\n {dict.drag_handle.delete_menuitem}\r\n </RemoveBlockItem>\r\n <BlockColorsItem {...props}>\r\n {dict.drag_handle.colors_menuitem}\r\n </BlockColorsItem>\r\n <TableRowHeaderItem {...(props as any)}>\r\n {dict.drag_handle.header_row_menuitem}\r\n </TableRowHeaderItem>\r\n <TableColumnHeaderItem {...(props as any)}>\r\n {dict.drag_handle.header_column_menuitem}\r\n </TableColumnHeaderItem>\r\n <TableAlignmentItems block={props.block} />\r\n </DragHandleMenu>\r\n );\r\n};\r\n","\"use client\";\r\n\r\n/**\r\n * Notion 스타일 FOCUS 기반 테이블 grip 컨트롤러.\r\n *\r\n * 동작(Notion 레퍼런스):\r\n * 1) 셀 focus 시 → 그 셀 테두리에 하이라이트(focus 보더) 표시\r\n * 2) 셀 focus 시 → 상(열)·좌(행)·우(셀) 보더에 \"짧고 두꺼운 gutter 바\"만 표시\r\n * (발판 grip은 아직 노출하지 않음)\r\n * 3) gutter에 hover 시 → 그 자리에 발판 grip(드래그 점 / 드롭다운 화살표) 노출\r\n * 4) grip 클릭 시 → 드롭다운 메뉴\r\n *\r\n * BlockNoteView는 tableHandles={false}로 기본 hover 핸들을 끄고, 이 컨트롤러를\r\n * 자식으로 렌더한다. grip/메뉴/드래그는 BlockNote 공개 컴포넌트(TableHandle,\r\n * TableCellButton)와 코어 editor.tableHandles 메서드를 재사용한다.\r\n *\r\n * 핵심 사실:\r\n * - tableHandles={false}는 React 컨트롤러만 끄고 코어 플러그인은 유지된다 →\r\n * colDragStart/rowDragStart/dragEnd/메뉴 동작 재사용 가능.\r\n * - 메뉴 동작(add/remove)은 우리가 넘기는 index prop으로 동작하므로 안전하다.\r\n * - 드래그만 코어 hover 상태에 의존 → gutter pointer-enter 시 합성 mousemove로\r\n * 코어 인덱스를 포커스 셀과 동기화한다(TS-private view 미접근).\r\n * - grip을 클릭/드래그하면 ProseMirror 선택이 셀 밖으로 바뀌어 focused가 즉시\r\n * 해제될 수 있다 → 상호작용(pointerdown/메뉴/드래그) 동안 recompute를 freeze.\r\n */\r\n\r\nimport {\r\n BlockSchema,\r\n InlineContentSchema,\r\n StyleSchema,\r\n} from \"@blocknote/core\";\r\nimport {\r\n ExtendButton,\r\n TableCellButton,\r\n TableHandle,\r\n useBlockNoteEditor,\r\n useEditorContentOrSelectionChange,\r\n useExtendButtonsPositioning,\r\n useUIPluginState,\r\n} from \"@blocknote/react\";\r\nimport { autoUpdate, FloatingPortal } from \"@floating-ui/react\";\r\nimport type { DragEvent as ReactDragEvent } from \"react\";\r\nimport { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\r\nimport {\r\n useFocusedCellHandlePositioning,\r\n useTableCornerPositioning,\r\n} from \"./hooks/useFocusedCellHandlePositioning\";\r\nimport { LumirTableCellMenu } from \"./color/LumirColorControls\";\r\nimport {\r\n measureTableForScale,\r\n setTableScalePreview,\r\n commitTableScale,\r\n} from \"../extensions/tableScaling\";\r\nimport { findTableNodePos } from \"../utils/prosemirror-table-utils\";\r\nimport {\r\n TABLE_SCALE_MIN_COL_WIDTH,\r\n TABLE_SCALE_MAX,\r\n ROW_RESIZE_MIN_HEIGHT,\r\n} from \"../constants/limits\";\r\n\r\ntype FocusedCell = {\r\n block: any;\r\n rowIndex: number;\r\n colIndex: number;\r\n cellEl: HTMLElement;\r\n tbodyEl: HTMLElement; // 표(tbody) — gutter 표-가장자리 배치 + 열/행 하이라이트 범위용\r\n widgetContainer: HTMLElement;\r\n};\r\n\r\n/**\r\n * 포커스된 셀 중앙에 합성 mousemove를 디스패치해 코어 TableHandles 플러그인의\r\n * hover 상태(rowIndex/colIndex/referencePosCell/tablePos/...)를 포커스 셀과\r\n * 동기화한다. 코어 리스너(pmView.dom)가 직접 받아 \"cell\" 분기를 실행한다.\r\n * 마우스 버튼이 눌리지 않은 상태(pointer-enter)에서만 호출해야 한다.\r\n */\r\nfunction syncCoreHoverToFocusedCell(cellEl: HTMLElement) {\r\n const r = cellEl.getBoundingClientRect();\r\n cellEl.dispatchEvent(\r\n new MouseEvent(\"mousemove\", {\r\n bubbles: true,\r\n cancelable: true,\r\n view: window,\r\n clientX: r.left + r.width / 2,\r\n clientY: r.top + r.height / 2,\r\n }),\r\n );\r\n}\r\n\r\nexport function LumirTableHandlesController() {\r\n const editor = useBlockNoteEditor<\r\n BlockSchema,\r\n InlineContentSchema,\r\n StyleSchema\r\n >();\r\n\r\n const [focused, setFocused] = useState<FocusedCell | null>(null);\r\n const [menuContainerRef, setMenuContainerRef] =\r\n useState<HTMLDivElement | null>(null);\r\n // overlay 엘리먼트는 FloatingPortal로 한 틱 늦게 마운트되므로 state-backed ref로\r\n // 잡아, 실제 마운트 시점에 사이징 effect가 재실행되도록 한다.\r\n const [overlayEl, setOverlayEl] = useState<HTMLDivElement | null>(null);\r\n\r\n // 어떤 gutter의 메뉴가 열려 있는지(활성 파란 발판 + 열림 동안 발판 유지용).\r\n const [openMenu, setOpenMenu] = useState<\"col\" | \"row\" | \"cell\" | null>(null);\r\n\r\n // 상호작용 중에는 selection 변경으로 focused가 해제되지 않도록 recompute를 멈춘다.\r\n const frozenRef = useRef(false);\r\n const menuOpenRef = useRef(false);\r\n const draggingRef = useRef(false);\r\n\r\n // 그립 메뉴의 행/열 삭제는 코어 removeRowOrColumn(= view.tablePos/selection 의존)을\r\n // 쓰는데, grip 클릭으로 ProseMirror 선택이 표 밖으로 빠지면 그 경로가 실패한다(첫 열/행\r\n // 삭제 안 됨). 포커스된 표 id를 에디터에 노출해, LumirEditor의 삭제 래퍼가 선택/hover와\r\n // 무관하게 정확한 표를 찾도록 한다. focused는 상호작용 중 freeze되어 유지된다.\r\n useEffect(() => {\r\n (editor as any).__lumirActiveTableId = focused?.block?.id ?? null;\r\n }, [editor, focused]);\r\n\r\n const recompute = useCallback(() => {\r\n if (frozenRef.current) {\r\n return;\r\n }\r\n\r\n const th = editor.tableHandles;\r\n const view = editor.prosemirrorView;\r\n if (!th || !view) {\r\n setFocused(null);\r\n return;\r\n }\r\n\r\n let cellEl: HTMLElement | null = null;\r\n try {\r\n const { node } = view.domAtPos(view.state.selection.from);\r\n const el =\r\n node.nodeType === Node.TEXT_NODE\r\n ? (node.parentElement as HTMLElement | null)\r\n : (node as HTMLElement);\r\n cellEl = (el?.closest?.(\"td, th\") as HTMLElement | null) ?? null;\r\n } catch {\r\n cellEl = null;\r\n }\r\n if (!cellEl || !cellEl.isConnected) {\r\n setFocused(null);\r\n return;\r\n }\r\n\r\n const blockId = cellEl.closest(\"[data-id]\")?.getAttribute(\"data-id\");\r\n const block = blockId ? editor.getBlock(blockId) : undefined;\r\n if (!block || block.type !== \"table\") {\r\n setFocused(null);\r\n return;\r\n }\r\n\r\n const cellSel = th.getCellSelection();\r\n if (!cellSel) {\r\n setFocused(null);\r\n return;\r\n }\r\n\r\n const widgetContainer = cellEl\r\n .closest(\".tableWrapper\")\r\n ?.querySelector(\".table-widgets-container\") as HTMLElement | null;\r\n const tbodyEl = cellEl.closest(\"tbody\") as HTMLElement | null;\r\n if (!widgetContainer || !tbodyEl) {\r\n setFocused(null);\r\n return;\r\n }\r\n\r\n setFocused({\r\n block,\r\n rowIndex: cellSel.from.row,\r\n colIndex: cellSel.from.col,\r\n cellEl,\r\n tbodyEl,\r\n widgetContainer,\r\n });\r\n }, [editor]);\r\n\r\n useEditorContentOrSelectionChange(recompute, editor);\r\n useEffect(() => {\r\n recompute();\r\n }, [recompute]);\r\n\r\n // pointerdown으로 freeze한 상호작용이 메뉴/드래그로 이어지지 않은 경우의 안전장치.\r\n useEffect(() => {\r\n const onUp = () => {\r\n requestAnimationFrame(() => {\r\n if (!menuOpenRef.current && !draggingRef.current && frozenRef.current) {\r\n frozenRef.current = false;\r\n recompute();\r\n }\r\n });\r\n };\r\n window.addEventListener(\"pointerup\", onUp);\r\n return () => window.removeEventListener(\"pointerup\", onUp);\r\n }, [recompute]);\r\n\r\n // 활성 보더 오버레이 위치/크기 갱신(스크롤/리사이즈 자동 추적).\r\n // openMenu에 따라 범위가 달라진다: 열 메뉴→열 전체, 행 메뉴→행 전체, 그 외→셀.\r\n useEffect(() => {\r\n const f = focused;\r\n if (!f || !overlayEl) {\r\n return;\r\n }\r\n // 보더 두께(2px)의 절반 = PAD(1px). 정수/DPR 반올림으로 거터와 정렬 유지.\r\n const PAD = 1;\r\n const update = () => {\r\n const cr = f.cellEl.getBoundingClientRect();\r\n const tr = f.tbodyEl.getBoundingClientRect();\r\n const kr = f.widgetContainer.getBoundingClientRect();\r\n // 활성 보더 범위: 열 메뉴=열 전체(표 상하), 행 메뉴=행 전체(표 좌우), 그 외=셀\r\n const x1 = openMenu === \"row\" ? tr.left : cr.left;\r\n const y1 = openMenu === \"col\" ? tr.top : cr.top;\r\n const x2 = openMenu === \"row\" ? tr.right : cr.right;\r\n const y2 = openMenu === \"col\" ? tr.bottom : cr.bottom;\r\n const dpr = window.devicePixelRatio || 1;\r\n const rd = (v: number) => Math.round(v * dpr) / dpr;\r\n const left = rd(x1 - kr.left) - PAD;\r\n const top = rd(y1 - kr.top) - PAD;\r\n const right = rd(x2 - kr.left) + PAD;\r\n const bottom = rd(y2 - kr.top) + PAD;\r\n overlayEl.style.transform = `translate(${left}px, ${top}px)`;\r\n overlayEl.style.width = `${right - left}px`;\r\n overlayEl.style.height = `${bottom - top}px`;\r\n };\r\n update();\r\n const stopAutoUpdate = autoUpdate(f.cellEl, overlayEl, update);\r\n // 다른 행 높이 변경(행 리사이즈 등)으로 표가 reflow되면 focus 셀 자체는 크기가\r\n // 안 변해 autoUpdate가 발화하지 않는다. 특히 \"아래\" 행이 커지면 widgetContainer가\r\n // 내려가 오버레이가 셀 밖으로 드리프트한다 → tbody 크기를 직접 관찰해 재포지셔닝.\r\n const ro = new ResizeObserver(update);\r\n ro.observe(f.tbodyEl);\r\n return () => {\r\n stopAutoUpdate();\r\n ro.disconnect();\r\n };\r\n }, [focused, overlayEl, openMenu]);\r\n\r\n const cellEl = focused?.cellEl ?? null;\r\n const tbodyEl = focused?.tbodyEl ?? null;\r\n const show = focused !== null;\r\n const rowHandle = useFocusedCellHandlePositioning(cellEl, tbodyEl, \"row\", show);\r\n const colHandle = useFocusedCellHandlePositioning(cellEl, tbodyEl, \"col\", show);\r\n const cellHandle = useFocusedCellHandlePositioning(\r\n cellEl,\r\n tbodyEl,\r\n \"cell\",\r\n show,\r\n );\r\n const th = editor.tableHandles;\r\n\r\n // 표 우측/하단 hover 시 \"행/열 추가\" ExtendButton — 코어 hover 상태 기반(focus와 독립).\r\n const coreState = useUIPluginState(\r\n editor.tableHandles!.onUpdate.bind(editor.tableHandles!),\r\n );\r\n const { addOrRemoveColumnsButton, addOrRemoveRowsButton } =\r\n useExtendButtonsPositioning(\r\n coreState?.showAddOrRemoveColumnsButton || false,\r\n coreState?.showAddOrRemoveRowsButton || false,\r\n coreState?.referencePosTable || null,\r\n );\r\n const onStartExtend = useCallback(() => {\r\n editor.tableHandles?.freezeHandles();\r\n }, [editor]);\r\n const onEndExtend = useCallback(() => {\r\n editor.tableHandles?.unfreezeHandles();\r\n }, [editor]);\r\n\r\n // 표 우하단 코너 스케일 hit-zone(focus 독립, 코어 hover 기반 → 셀 클릭 없이도 노출).\r\n const tableCorner = useTableCornerPositioning(\r\n coreState?.referencePosTable ?? null,\r\n !!coreState?.widgetContainer,\r\n );\r\n\r\n // 메뉴 열림/닫힘 핸들러(컨트롤별). 열림 동안 frozen 유지 + openMenu로 활성 표시.\r\n const menuHandlers = useMemo(() => {\r\n const mk = (kind: \"col\" | \"row\" | \"cell\") => ({\r\n freeze: () => {\r\n menuOpenRef.current = true;\r\n frozenRef.current = true;\r\n setOpenMenu(kind);\r\n editor.tableHandles?.freezeHandles();\r\n },\r\n unfreeze: () => {\r\n menuOpenRef.current = false;\r\n frozenRef.current = false;\r\n setOpenMenu(null);\r\n editor.tableHandles?.unfreezeHandles();\r\n recompute();\r\n },\r\n });\r\n return { col: mk(\"col\"), row: mk(\"row\"), cell: mk(\"cell\") };\r\n }, [editor, recompute]);\r\n\r\n const onGutterPointerDown = useCallback(() => {\r\n frozenRef.current = true;\r\n }, []);\r\n\r\n const onGutterPointerEnter = useCallback(\r\n (e: { buttons: number }) => {\r\n if (e.buttons === 0 && focused) {\r\n syncCoreHoverToFocusedCell(focused.cellEl);\r\n }\r\n },\r\n [focused],\r\n );\r\n\r\n const makeDragStart = useCallback(\r\n (dir: \"row\" | \"col\") => (e: ReactDragEvent) => {\r\n draggingRef.current = true;\r\n frozenRef.current = true;\r\n if (dir === \"row\") {\r\n editor.tableHandles?.rowDragStart(e as unknown as DragEvent);\r\n } else {\r\n editor.tableHandles?.colDragStart(e as unknown as DragEvent);\r\n }\r\n },\r\n [editor],\r\n );\r\n\r\n const onDragEnd = useCallback(() => {\r\n editor.tableHandles?.dragEnd();\r\n draggingRef.current = false;\r\n frozenRef.current = false;\r\n recompute();\r\n }, [editor, recompute]);\r\n\r\n const noop = useCallback(() => {}, []);\r\n\r\n // 우하단 코너 드래그 → 표 전체 종횡비 고정 스케일.\r\n // hit-zone은 에디터 DOM 밖(오버레이)이라 row/column 리사이즈와 충돌하지 않는다.\r\n // 대상 표는 코어 hover 상태(coreState.block) → 셀 focus 없이도 동작.\r\n // 드래그 동안 freezeHandles로 코어 hover 상태를 고정(마우스가 표를 벗어나도 유지).\r\n const onScaleStart = useCallback(\r\n (e: React.PointerEvent) => {\r\n e.preventDefault();\r\n e.stopPropagation();\r\n const view = (editor as any).prosemirrorView;\r\n const blockId = coreState?.block?.id;\r\n if (!view || !blockId) return;\r\n const tablePos = findTableNodePos((editor as any)._tiptapEditor, blockId);\r\n if (tablePos < 0) return;\r\n const base = measureTableForScale(view, tablePos);\r\n if (!base) return;\r\n\r\n const minScale = Math.max(\r\n TABLE_SCALE_MIN_COL_WIDTH / Math.max(1, Math.min(...base.colWidths)),\r\n ROW_RESIZE_MIN_HEIGHT / Math.max(1, Math.min(...base.rowHeights)),\r\n );\r\n const startX = e.clientX;\r\n const startY = e.clientY;\r\n let current = base;\r\n editor.tableHandles?.freezeHandles();\r\n\r\n const onMove = (me: PointerEvent) => {\r\n if (me.buttons === 0) return onUp();\r\n const newW = base.origW + (me.clientX - startX);\r\n const newH = base.origH + (me.clientY - startY);\r\n const scale = Math.min(\r\n TABLE_SCALE_MAX,\r\n Math.max(minScale, Math.max(newW / base.origW, newH / base.origH)),\r\n );\r\n current = { ...base, scale };\r\n setTableScalePreview(view, current);\r\n };\r\n const onUp = () => {\r\n window.removeEventListener(\"pointermove\", onMove);\r\n window.removeEventListener(\"pointerup\", onUp);\r\n // ⚠️ rowResizing 교훈: 프리뷰 제거 후 커밋(스타일 충돌 방지).\r\n setTableScalePreview(view, null);\r\n commitTableScale(view, current);\r\n editor.tableHandles?.unfreezeHandles();\r\n };\r\n window.addEventListener(\"pointermove\", onMove);\r\n window.addEventListener(\"pointerup\", onUp);\r\n },\r\n [editor, coreState],\r\n );\r\n\r\n return (\r\n <>\r\n {/* 메뉴 컨테이너: grip 메뉴가 createPortal로 그려지는 대상. portal 밖에 둔다. */}\r\n <div ref={setMenuContainerRef} />\r\n {th && focused && menuContainerRef && (\r\n <FloatingPortal root={focused.widgetContainer}>\r\n {/* focus 보더 오버레이 (셀을 덮는 비클릭 테두리) */}\r\n <div ref={setOverlayEl} className=\"lumir-tbl-cell-focus\" />\r\n\r\n {/* 상(top) gutter → 열(column) */}\r\n {colHandle.isMounted && (\r\n <div\r\n ref={colHandle.ref}\r\n style={colHandle.style}\r\n className={\r\n \"lumir-tbl-gutter-wrap lumir-tbl-gutter-wrap--col\" +\r\n (openMenu === \"col\" ? \" lumir-tbl-gutter-wrap--active\" : \"\")\r\n }\r\n onPointerEnter={onGutterPointerEnter}\r\n onPointerDown={onGutterPointerDown}\r\n >\r\n <span className=\"lumir-tbl-gutter\" />\r\n <div className=\"lumir-tbl-grip\">\r\n <TableHandle\r\n editor={editor as any}\r\n orientation=\"column\"\r\n index={focused.colIndex}\r\n block={focused.block}\r\n dragStart={makeDragStart(\"col\")}\r\n dragEnd={onDragEnd}\r\n freezeHandles={menuHandlers.col.freeze}\r\n unfreezeHandles={menuHandlers.col.unfreeze}\r\n menuContainer={menuContainerRef}\r\n showOtherSide={noop}\r\n hideOtherSide={noop}\r\n />\r\n </div>\r\n </div>\r\n )}\r\n\r\n {/* 좌(left) gutter → 행(row) */}\r\n {rowHandle.isMounted && (\r\n <div\r\n ref={rowHandle.ref}\r\n style={rowHandle.style}\r\n className={\r\n \"lumir-tbl-gutter-wrap lumir-tbl-gutter-wrap--row\" +\r\n (openMenu === \"row\" ? \" lumir-tbl-gutter-wrap--active\" : \"\")\r\n }\r\n onPointerEnter={onGutterPointerEnter}\r\n onPointerDown={onGutterPointerDown}\r\n >\r\n <span className=\"lumir-tbl-gutter\" />\r\n <div className=\"lumir-tbl-grip\">\r\n <TableHandle\r\n editor={editor as any}\r\n orientation=\"row\"\r\n index={focused.rowIndex}\r\n block={focused.block}\r\n dragStart={makeDragStart(\"row\")}\r\n dragEnd={onDragEnd}\r\n freezeHandles={menuHandlers.row.freeze}\r\n unfreezeHandles={menuHandlers.row.unfreeze}\r\n menuContainer={menuContainerRef}\r\n showOtherSide={noop}\r\n hideOtherSide={noop}\r\n />\r\n </div>\r\n </div>\r\n )}\r\n\r\n {/* 우(right) → 파란 선택 앵커(rest) / hover 시 셀(색상·분할) 메뉴.\r\n 행/열 grip 메뉴가 열려 범위 하이라이트 중에는 노출하지 않는다. */}\r\n {cellHandle.isMounted && openMenu !== \"col\" && openMenu !== \"row\" && (\r\n <div\r\n ref={cellHandle.ref}\r\n style={cellHandle.style}\r\n className={\r\n \"lumir-tbl-gutter-wrap lumir-tbl-gutter-wrap--cell\" +\r\n (openMenu === \"cell\" ? \" lumir-tbl-gutter-wrap--active\" : \"\")\r\n }\r\n onPointerDown={onGutterPointerDown}\r\n >\r\n <span className=\"lumir-tbl-gutter\" />\r\n <div className=\"lumir-tbl-grip\">\r\n <TableCellButton\r\n editor={editor as any}\r\n rowIndex={focused.rowIndex}\r\n colIndex={focused.colIndex}\r\n block={focused.block}\r\n tableCellMenu={LumirTableCellMenu as any}\r\n menuContainer={menuContainerRef}\r\n freezeHandles={menuHandlers.cell.freeze}\r\n unfreezeHandles={menuHandlers.cell.unfreeze}\r\n />\r\n </div>\r\n </div>\r\n )}\r\n </FloatingPortal>\r\n )}\r\n\r\n {/* 표 우측/하단 hover 시 행/열 추가(ExtendButton). focus와 무관하게 코어 hover 상태로 표시. */}\r\n {th && coreState?.widgetContainer && (\r\n <FloatingPortal root={coreState.widgetContainer}>\r\n <div ref={addOrRemoveRowsButton.ref} style={addOrRemoveRowsButton.style}>\r\n <ExtendButton\r\n editor={editor as any}\r\n orientation=\"addOrRemoveRows\"\r\n block={coreState.block}\r\n onMouseDown={onStartExtend}\r\n onMouseUp={onEndExtend}\r\n />\r\n </div>\r\n <div\r\n ref={addOrRemoveColumnsButton.ref}\r\n style={addOrRemoveColumnsButton.style}\r\n >\r\n <ExtendButton\r\n editor={editor as any}\r\n orientation=\"addOrRemoveColumns\"\r\n block={coreState.block}\r\n onMouseDown={onStartExtend}\r\n onMouseUp={onEndExtend}\r\n />\r\n </div>\r\n\r\n {/* 우하단 코너 → 표 전체 종횡비 고정 스케일(투명 hit-zone, focus 독립).\r\n hover된 표 기준이라 셀 클릭 없이도 동작. */}\r\n {tableCorner.isMounted && (\r\n <div\r\n ref={tableCorner.ref}\r\n style={tableCorner.style}\r\n className=\"lumir-tbl-scale-handle\"\r\n title=\"표 크기 조절 (종횡비 고정)\"\r\n onPointerDown={onScaleStart}\r\n />\r\n )}\r\n </FloatingPortal>\r\n )}\r\n </>\r\n );\r\n}\r\n","import {\r\n autoUpdate,\r\n offset,\r\n useFloating,\r\n useTransitionStyles,\r\n} from \"@floating-ui/react\";\r\nimport type { CSSProperties } from \"react\";\r\nimport { useEffect, useMemo } from \"react\";\r\n\r\n/**\r\n * Notion 스타일 gutter/grip 배치용 positioner (표 가장자리 기준).\r\n *\r\n * Notion 레퍼런스: 열 gutter는 표 **상단** 가장자리(focus 열 중앙), 행 gutter는 표\r\n * **좌측** 가장자리(focus 행 중앙), 셀 핸들은 셀 **우상단 모서리**에 놓인다.\r\n * 따라서 셀이 아니라 표(tbody) 가장자리를 기준으로 배치해야 하므로, floating-ui\r\n * **virtual element**(`{ getBoundingClientRect, contextElement }`)를 reference로 쓴다.\r\n *\r\n * - \"col\" → 표 상단 + 열 중앙 (zero-height ref, placement \"top\")\r\n * - \"row\" → 표 좌측 + 행 중앙 (zero-width ref, placement \"left\")\r\n * - \"cell\" → 셀 우측 보더 (cell rect, placement \"right\")\r\n *\r\n * `contextElement: cellEl`을 넣어야 `autoUpdate`가 관찰할 실제 엘리먼트를 가짐\r\n * (스크롤/리사이즈 시 gutter가 가장자리에 고정 유지됨).\r\n */\r\nexport function useFocusedCellHandlePositioning(\r\n cellEl: HTMLElement | null,\r\n tbodyEl: HTMLElement | null,\r\n orientation: \"row\" | \"col\" | \"cell\" | \"corner\",\r\n show: boolean,\r\n): {\r\n isMounted: boolean;\r\n ref: (node: HTMLElement | null) => void;\r\n style: CSSProperties;\r\n} {\r\n const { refs, floatingStyles, context, update } = useFloating({\r\n open: show,\r\n placement:\r\n orientation === \"row\"\r\n ? \"left\"\r\n : orientation === \"col\"\r\n ? \"top\"\r\n : orientation === \"corner\"\r\n ? \"bottom-start\"\r\n : \"right\",\r\n // col/row/cell: 가장자리에 14px hit-area 중앙 정렬(-7).\r\n // corner: 18px hit-zone이 표 우하단 모서리에 걸치도록 위/좌로 살짝 당김(모서리 hover 자연).\r\n middleware: [\r\n offset(\r\n orientation === \"corner\" ? { mainAxis: -6, crossAxis: -6 } : -7,\r\n ),\r\n ],\r\n whileElementsMounted: autoUpdate,\r\n });\r\n\r\n const { isMounted, styles } = useTransitionStyles(context);\r\n\r\n // 다른 행 높이 변경으로 표가 reflow되면(특히 focus 셀보다 아래 행이 커지면) focus 셀\r\n // 자체는 크기가 안 변해 autoUpdate가 발화하지 않아 gutter/grip이 어긋난다.\r\n // tbody 크기를 직접 관찰해 재포지셔닝한다.\r\n useEffect(() => {\r\n if (!show || !tbodyEl) {\r\n return;\r\n }\r\n const ro = new ResizeObserver(() => update());\r\n ro.observe(tbodyEl);\r\n return () => ro.disconnect();\r\n }, [show, tbodyEl, update]);\r\n\r\n useEffect(() => {\r\n if (!cellEl) {\r\n refs.setReference(null);\r\n return;\r\n }\r\n refs.setReference({\r\n contextElement: cellEl,\r\n getBoundingClientRect: () => {\r\n const c = cellEl.getBoundingClientRect();\r\n const t = tbodyEl?.getBoundingClientRect() ?? c;\r\n if (orientation === \"col\") {\r\n // 표 상단 가장자리 + focus 셀의 열 폭(zero-height)\r\n return new DOMRect(c.left, t.top, c.width, 0);\r\n }\r\n if (orientation === \"row\") {\r\n // 표 좌측 가장자리 + focus 셀의 행 높이(zero-width)\r\n return new DOMRect(t.left, c.top, 0, c.height);\r\n }\r\n if (orientation === \"corner\") {\r\n // 표 우하단 모서리의 영점(zero-size) → placement \"bottom-start\"로 핸들 좌상단을\r\n // 모서리에 맞닿게(핸들이 모서리에서 우·하로 뻗음).\r\n return new DOMRect(t.right, t.bottom, 0, 0);\r\n }\r\n // 셀 핸들: 셀 rect (placement \"right\" → 우측 보더)\r\n return c;\r\n },\r\n });\r\n }, [cellEl, tbodyEl, orientation, refs]);\r\n\r\n return useMemo(\r\n () => ({\r\n isMounted,\r\n ref: refs.setFloating,\r\n // display는 CSS 클래스에서 제어한다(absolute 자식 stacking).\r\n style: {\r\n ...styles,\r\n ...floatingStyles,\r\n },\r\n }),\r\n [floatingStyles, isMounted, refs.setFloating, styles],\r\n );\r\n}\r\n\r\n/**\r\n * 표 우하단 코너 스케일 hit-zone 배치(focus 독립, 코어 hover 상태 기반).\r\n *\r\n * focus가 아니라 `coreState.referencePosTable`(hover된 표의 DOMRect)을 기준으로 잡으므로\r\n * 셀을 클릭하지 않아도 표 hover만으로 코너 hit-zone이 노출된다(ExtendButton과 동일 패턴).\r\n * 코너에 살짝 안쪽으로 걸치도록 배치해, hover 시 마우스가 표 위에 남아 coreState가 유지된다.\r\n */\r\nexport function useTableCornerPositioning(\r\n referencePosTable: DOMRect | null,\r\n show: boolean,\r\n): {\r\n isMounted: boolean;\r\n ref: (node: HTMLElement | null) => void;\r\n style: CSSProperties;\r\n} {\r\n const { refs, floatingStyles, context } = useFloating({\r\n open: show,\r\n placement: \"bottom-start\",\r\n // 18px hit-zone을 모서리에서 안쪽(위/좌)으로 당겨 표 위에 걸치게 한다.\r\n middleware: [offset({ mainAxis: -12, crossAxis: -12 })],\r\n whileElementsMounted: autoUpdate,\r\n });\r\n const { isMounted, styles } = useTransitionStyles(context);\r\n\r\n useEffect(() => {\r\n if (!referencePosTable) {\r\n refs.setReference(null);\r\n return;\r\n }\r\n refs.setReference({\r\n getBoundingClientRect: () =>\r\n new DOMRect(referencePosTable.right, referencePosTable.bottom, 0, 0),\r\n });\r\n }, [referencePosTable, refs]);\r\n\r\n return useMemo(\r\n () => ({\r\n isMounted,\r\n ref: refs.setFloating,\r\n style: { ...styles, ...floatingStyles },\r\n }),\r\n [floatingStyles, isMounted, refs.setFloating, styles],\r\n );\r\n}\r\n","import type { DefaultPartialBlock } from \"../types\";\r\n\r\n/**\r\n * BlockNote가 모델링하지 않는 표 셀 속성(verticalAlignment, rowHeight 등)을 블록 JSON에\r\n * 주입하는 직렬화 헬퍼.\r\n *\r\n * BlockNote의 nodeToBlock(contentNodeToTableContent)은 고정된 prop 집합\r\n * (colspan/rowspan/backgroundColor/textColor/textAlignment)만 cell.props에 담으므로,\r\n * 커스텀 글로벌 attribute는 블록 JSON에서 누락된다. 이 모듈은 ProseMirror 상태에서\r\n * 직접 읽어 cell.props에 다시 주입한다(onContentChange 직전).\r\n *\r\n * 로드(역방향)는 별도 코드가 필요 없다: BlockNote blockToNode가 `...cell.props`를 노드\r\n * attr로 그대로 펼치므로, 글로벌 attribute로 등록만 돼 있으면 자동 복원된다.\r\n */\r\n\r\n/**\r\n * 각 셀 속성의 \"기본값\"(= 주입에서 제외할 값). 기본값이거나 null/undefined면 주입하지 않아\r\n * 블록 JSON을 불필요하게 키우지 않는다.\r\n */\r\nconst CELL_ATTR_DEFAULTS: Record<string, unknown> = {\r\n verticalAlignment: \"top\",\r\n rowHeight: null,\r\n};\r\n\r\nfunction isMeaningful(attr: string, value: unknown): boolean {\r\n if (value === null || value === undefined) {\r\n return false;\r\n }\r\n return value !== CELL_ATTR_DEFAULTS[attr];\r\n}\r\n\r\ntype CellAttrs = {\r\n row: number;\r\n col: number;\r\n props: Record<string, unknown>;\r\n};\r\n\r\n/**\r\n * ProseMirror 문서에서 각 table 블록 id → 셀별(주입 대상 attr만 담은) props 맵을 구축한다.\r\n */\r\nfunction buildTableCellAttrMap(\r\n doc: any,\r\n attrs: string[],\r\n): Map<string, CellAttrs[]> {\r\n const result = new Map<string, CellAttrs[]>();\r\n\r\n doc.descendants((node: any) => {\r\n if (node.type.name === \"blockContainer\") {\r\n const blockId = node.attrs?.id;\r\n const contentNode = node.firstChild;\r\n if (blockId && contentNode?.type.name === \"table\") {\r\n const cells: CellAttrs[] = [];\r\n let rowIndex = 0;\r\n contentNode.forEach((rowNode: any) => {\r\n if (rowNode.type.name === \"tableRow\") {\r\n let colIndex = 0;\r\n rowNode.forEach((cellNode: any) => {\r\n const props: Record<string, unknown> = {};\r\n for (const attr of attrs) {\r\n const value = cellNode.attrs?.[attr];\r\n if (isMeaningful(attr, value)) {\r\n props[attr] = value;\r\n }\r\n }\r\n if (Object.keys(props).length > 0) {\r\n cells.push({ row: rowIndex, col: colIndex, props });\r\n }\r\n colIndex++;\r\n });\r\n rowIndex++;\r\n }\r\n });\r\n if (cells.length > 0) {\r\n result.set(blockId, cells);\r\n }\r\n }\r\n return false;\r\n }\r\n });\r\n\r\n return result;\r\n}\r\n\r\nfunction patchBlocks(\r\n blocks: DefaultPartialBlock[],\r\n cellAttrMap: Map<string, CellAttrs[]>,\r\n): DefaultPartialBlock[] {\r\n return blocks.map((block) => {\r\n if (block.type !== \"table\" || !block.id || !block.content) {\r\n if (block.children && block.children.length > 0) {\r\n return {\r\n ...block,\r\n children: patchBlocks(\r\n block.children as DefaultPartialBlock[],\r\n cellAttrMap,\r\n ),\r\n };\r\n }\r\n return block;\r\n }\r\n\r\n const cells = cellAttrMap.get(block.id);\r\n if (!cells || cells.length === 0) {\r\n return block;\r\n }\r\n\r\n const content = block.content as any;\r\n if (content.type !== \"tableContent\" || !content.rows) {\r\n return block;\r\n }\r\n\r\n const newRows = content.rows.map((row: any, rowIndex: number) => {\r\n const newCells = row.cells.map((cell: any, colIndex: number) => {\r\n const match = cells.find(\r\n (c) => c.row === rowIndex && c.col === colIndex,\r\n );\r\n if (!match) {\r\n return cell;\r\n }\r\n if (cell && typeof cell === \"object\" && cell.type === \"tableCell\") {\r\n return {\r\n ...cell,\r\n props: { ...cell.props, ...match.props },\r\n };\r\n }\r\n return cell;\r\n });\r\n return { ...row, cells: newCells };\r\n });\r\n\r\n return {\r\n ...block,\r\n content: { ...content, rows: newRows },\r\n };\r\n });\r\n}\r\n\r\n/**\r\n * blocks 배열의 table 블록을 찾아, ProseMirror 상태에서 읽은 셀별 attr(`attrs`로 지정)을\r\n * cell.props에 주입한다. 한 번의 문서 순회로 여러 attr을 동시에 처리한다.\r\n *\r\n * @param attrs 주입할 셀 노드 attr 이름들(예: [\"verticalAlignment\", \"rowHeight\"])\r\n */\r\nexport function injectTableCellAttrs(\r\n blocks: DefaultPartialBlock[],\r\n editor: any,\r\n attrs: string[],\r\n): DefaultPartialBlock[] {\r\n const tiptap = editor?._tiptapEditor;\r\n if (!tiptap) {\r\n return blocks;\r\n }\r\n\r\n const { doc } = tiptap.state;\r\n const cellAttrMap = buildTableCellAttrMap(doc, attrs);\r\n if (cellAttrMap.size === 0) {\r\n return blocks;\r\n }\r\n\r\n return patchBlocks(blocks, cellAttrMap);\r\n}\r\n\r\n/**\r\n * verticalAlignment만 주입하는 하위호환 래퍼. 기존 호출부 호환을 위해 유지.\r\n * @deprecated 신규 호출부는 injectTableCellAttrs를 직접 사용.\r\n */\r\nexport function injectVerticalAlignment(\r\n blocks: DefaultPartialBlock[],\r\n editor: any,\r\n): DefaultPartialBlock[] {\r\n return injectTableCellAttrs(blocks, editor, [\"verticalAlignment\"]);\r\n}\r\n\r\n/** table 노드(블록 레벨) 보존 대상 attr과 기본값. */\r\nconst TABLE_BLOCK_ATTR_DEFAULTS: Record<string, unknown> = {\r\n tableAlignment: \"left\",\r\n};\r\n\r\n/**\r\n * ProseMirror 문서에서 table 블록 id → 블록 레벨 attr(tableAlignment 등) 맵을 만든다.\r\n * 셀이 아니라 table 노드 자체의 attr을 읽는다.\r\n */\r\nfunction buildTableBlockAttrMap(\r\n doc: any,\r\n attrs: string[],\r\n): Map<string, Record<string, unknown>> {\r\n const result = new Map<string, Record<string, unknown>>();\r\n\r\n doc.descendants((node: any) => {\r\n if (node.type.name === \"blockContainer\") {\r\n const blockId = node.attrs?.id;\r\n const contentNode = node.firstChild;\r\n if (blockId && contentNode?.type.name === \"table\") {\r\n const props: Record<string, unknown> = {};\r\n for (const attr of attrs) {\r\n const value = contentNode.attrs?.[attr];\r\n if (\r\n value !== null &&\r\n value !== undefined &&\r\n value !== TABLE_BLOCK_ATTR_DEFAULTS[attr]\r\n ) {\r\n props[attr] = value;\r\n }\r\n }\r\n if (Object.keys(props).length > 0) {\r\n result.set(blockId, props);\r\n }\r\n }\r\n return false;\r\n }\r\n return undefined;\r\n });\r\n\r\n return result;\r\n}\r\n\r\nfunction patchTableBlockProps(\r\n blocks: DefaultPartialBlock[],\r\n attrMap: Map<string, Record<string, unknown>>,\r\n): DefaultPartialBlock[] {\r\n return blocks.map((block) => {\r\n if (block.type === \"table\" && block.id && attrMap.has(block.id)) {\r\n return {\r\n ...block,\r\n props: { ...(block.props as any), ...attrMap.get(block.id) },\r\n } as DefaultPartialBlock;\r\n }\r\n if (block.children && block.children.length > 0) {\r\n return {\r\n ...block,\r\n children: patchTableBlockProps(\r\n block.children as DefaultPartialBlock[],\r\n attrMap,\r\n ),\r\n };\r\n }\r\n return block;\r\n });\r\n}\r\n\r\n/**\r\n * blocks 배열의 table 블록에, ProseMirror 상태에서 읽은 table 노드 블록 레벨 attr\r\n * (예: tableAlignment)을 block.props에 주입한다. BlockNote nodeToBlock이 table 노드의\r\n * 커스텀 attr을 block.props로 추출하지 않으므로 직접 주입한다.\r\n */\r\nexport function injectTableBlockAttrs(\r\n blocks: DefaultPartialBlock[],\r\n editor: any,\r\n): DefaultPartialBlock[] {\r\n const tiptap = editor?._tiptapEditor;\r\n if (!tiptap) {\r\n return blocks;\r\n }\r\n const { doc } = tiptap.state;\r\n const attrMap = buildTableBlockAttrMap(doc, [\"tableAlignment\"]);\r\n if (attrMap.size === 0) {\r\n return blocks;\r\n }\r\n return patchTableBlockProps(blocks, attrMap);\r\n}\r\n","import type { DefaultPartialBlock } from \"../types\";\r\n\r\n/**\r\n * 글자 크기(fontSize) 직렬화 호환 레이어.\r\n *\r\n * 배경:\r\n * - BlockNote는 인라인 `styles` 맵에 스키마에 없는 키가 있으면\r\n * `style ${name} not found in styleSchema` 예외를 던진다(blockToNode →\r\n * styledTextToNodes). 이미 배포된 구버전 SDK(≤0.4.15, fontSize 스펙 없음)가\r\n * `styles.fontSize`가 포함된 JSON을 initialContent로 받으면 에디터 생성이\r\n * 실패하며 React 트리가 크래시한다.\r\n * - 반면 styled-text 객체의 **형제(sibling) 키**는 BlockNote가 읽지 않으므로\r\n * 조용히 무시된다(크래시 없음).\r\n *\r\n * 전략:\r\n * - 저장/전달용 JSON(onContentChange 출력)에서는 `styles.fontSize`를 형제 키\r\n * `fontSize`로 내린다(lowerFontSize).\r\n * - 로드 시(initialContent)에는 형제 키를 다시 `styles.fontSize`로 올려\r\n * 신버전 에디터가 렌더링하게 한다(liftFontSize).\r\n *\r\n * ⚠️ 불변 규칙: 에디터 외부로 블록 JSON을 내보내는 모든 경로는 반드시\r\n * lowerFontSize를 거쳐야 한다(현재 출력 표면은 onContentChange 유일).\r\n * styles.fontSize가 저장 JSON에 유출되면 구버전 소비 앱이 크래시한다.\r\n */\r\n\r\n/**\r\n * 직렬화(저장) 포맷의 styled-text 항목.\r\n * fontSize는 styles 맵이 아닌 형제 키로 존재한다 — 구버전 SDK(≤0.4.15)는\r\n * styles만 순회하므로 이 키를 조용히 무시한다(파싱 오류 없음).\r\n */\r\nexport type SerializedStyledText = {\r\n type: \"text\";\r\n text: string;\r\n styles: Record<string, unknown>;\r\n /** 인라인 글자 크기(예: \"18px\"). 구버전 SDK에서는 무시됨 */\r\n fontSize?: string;\r\n};\r\n\r\ntype Dir = \"lower\" | \"lift\";\r\n\r\n/**\r\n * 배열 map 후 모든 요소가 원본과 동일하면 원본 배열 참조를 그대로 반환\r\n * (불필요한 재생성 방지 — table-vertical-alignment.ts의 불변 패턴 준수)\r\n */\r\nfunction mapPreservingRef<T>(arr: T[], fn: (item: T) => T): T[] {\r\n let changed = false;\r\n const next = arr.map((item) => {\r\n const mapped = fn(item);\r\n if (mapped !== item) changed = true;\r\n return mapped;\r\n });\r\n return changed ? next : arr;\r\n}\r\n\r\n/** styled-text 한 항목의 fontSize를 styles ↔ 형제 키 간 이동 */\r\nfunction mapStyledItem(item: any, dir: Dir): any {\r\n if (!item || typeof item !== \"object\" || item.type !== \"text\") {\r\n return item;\r\n }\r\n\r\n if (dir === \"lower\") {\r\n const fs = item.styles?.fontSize;\r\n if (fs == null) return item;\r\n const { fontSize: _removed, ...restStyles } = item.styles;\r\n return { ...item, styles: restStyles, fontSize: fs };\r\n }\r\n\r\n // lift: 형제 키 → styles.fontSize (둘 다 있으면 형제 키 우선)\r\n const fs = item.fontSize;\r\n if (fs == null) return item;\r\n const { fontSize: _lifted, ...rest } = item;\r\n return { ...rest, styles: { ...(item.styles ?? {}), fontSize: fs } };\r\n}\r\n\r\n/** 인라인 콘텐츠 항목 처리 — link는 중첩 content(StyledText[])를 재귀 */\r\nfunction mapInlineItem(node: any, dir: Dir): any {\r\n if (!node || typeof node !== \"object\") return node;\r\n if (node.type === \"link\" && Array.isArray(node.content)) {\r\n const content = mapPreservingRef(node.content, (n) =>\r\n mapStyledItem(n, dir),\r\n );\r\n return content === node.content ? node : { ...node, content };\r\n }\r\n return mapStyledItem(node, dir);\r\n}\r\n\r\n/** 테이블 셀 — `{type:\"tableCell\", content}` 객체와 평문 인라인 배열 두 형태 모두 처리 */\r\nfunction mapCell(cell: any, dir: Dir): any {\r\n if (cell && typeof cell === \"object\" && cell.type === \"tableCell\") {\r\n if (!Array.isArray(cell.content)) return cell;\r\n const content = mapPreservingRef(cell.content, (n) =>\r\n mapInlineItem(n, dir),\r\n );\r\n return content === cell.content ? cell : { ...cell, content };\r\n }\r\n if (Array.isArray(cell)) {\r\n return mapPreservingRef(cell, (n) => mapInlineItem(n, dir));\r\n }\r\n return cell;\r\n}\r\n\r\nfunction mapBlock(block: any, dir: Dir): any {\r\n if (!block || typeof block !== \"object\") return block;\r\n\r\n let next = block;\r\n const content = block.content;\r\n\r\n if (Array.isArray(content)) {\r\n // paragraph/heading/list 계열 인라인 콘텐츠\r\n const mapped = mapPreservingRef(content, (n) => mapInlineItem(n, dir));\r\n if (mapped !== content) next = { ...next, content: mapped };\r\n } else if (\r\n content &&\r\n typeof content === \"object\" &&\r\n content.type === \"tableContent\" &&\r\n Array.isArray(content.rows)\r\n ) {\r\n const rows = mapPreservingRef(content.rows, (row: any) => {\r\n if (!row || !Array.isArray(row.cells)) return row;\r\n const cells = mapPreservingRef(row.cells, (cell: any) =>\r\n mapCell(cell, dir),\r\n );\r\n return cells === row.cells ? row : { ...row, cells };\r\n });\r\n if (rows !== content.rows) next = { ...next, content: { ...content, rows } };\r\n }\r\n // content === undefined (image/video/htmlPreview/linkPreview) → 그대로 통과\r\n\r\n if (Array.isArray(block.children) && block.children.length > 0) {\r\n const children = mapPreservingRef(block.children, (b: any) =>\r\n mapBlock(b, dir),\r\n );\r\n if (children !== block.children) next = { ...next, children };\r\n }\r\n\r\n return next;\r\n}\r\n\r\n/**\r\n * 출력 방향: `styles.fontSize` → 형제 키 `fontSize`.\r\n * onContentChange로 내보내는 블록 JSON에 적용해 구버전 SDK 크래시를 방지한다.\r\n * 입력을 변형하지 않는다(불변).\r\n */\r\nexport function lowerFontSize(\r\n blocks: DefaultPartialBlock[],\r\n): DefaultPartialBlock[] {\r\n return mapPreservingRef(blocks as any[], (b) => mapBlock(b, \"lower\"));\r\n}\r\n\r\n/**\r\n * 입력 방향: 형제 키 `fontSize` → `styles.fontSize`.\r\n * initialContent를 에디터에 넘기기 전에 적용해 글자 크기를 복원한다.\r\n * 입력을 변형하지 않는다(불변).\r\n */\r\nexport function liftFontSize(\r\n blocks: DefaultPartialBlock[],\r\n): DefaultPartialBlock[] {\r\n return mapPreservingRef(blocks as any[], (b) => mapBlock(b, \"lift\"));\r\n}\r\n","import { CellSelection, TableMap, deleteRow } from \"prosemirror-tables\";\r\nimport { findTableNodePos } from \"./prosemirror-table-utils\";\r\n\r\n/**\r\n * 표(tablePos)의 각 행(tbody > tr)의 렌더 높이(px)를 PM 행 순서대로 측정한다.\r\n * 열 삭제로 rowspan 병합 셀이 collapse될 때 높이를 보존하기 위해 쓴다.\r\n * 측정 불가 시 null(→ 호출부는 높이 보존을 건너뜀).\r\n */\r\nfunction measureRowHeights(view: any, tablePos: number): number[] | null {\r\n try {\r\n const at = view?.domAtPos?.(tablePos + 1);\r\n let el: any = at?.node;\r\n if (el && el.nodeType === 3) el = el.parentElement;\r\n const tableEl: HTMLTableElement | null = el?.closest?.(\"table\") ?? null;\r\n const body = tableEl?.tBodies?.[0];\r\n if (!body) return null;\r\n return Array.from(body.rows).map((tr) =>\r\n Math.round(tr.getBoundingClientRect().height),\r\n );\r\n } catch {\r\n return null;\r\n }\r\n}\r\n\r\n/**\r\n * 표에서 한 열(col)을 제거한 **새 table 노드를 재구성**해 교체하는 트랜잭션을 만든다.\r\n *\r\n * 왜 재구성인가:\r\n * prosemirror-tables의 removeColumn(및 그를 쓰는 deleteColumn)은 열의 셀들을 개별\r\n * tr.delete로 지운다. 그런데 BlockNote의 tableRow content는 `tableRow+`(셀 1개 이상\r\n * 필수)라, 어떤 행의 \"유일한 셀\"이 삭제 대상이면 ProseMirror가 행을 비울 수 없어 **빈\r\n * 셀 껍데기**를 남긴다. 인접 열이 rowspan 병합돼 있으면(예: 2·3열 세로병합 후 1열 삭제)\r\n * 이 빈 셀이 rowspan 셀과 충돌(collision)하고, tableEditing의 fixTables가 이를 원복해\r\n * **열이 삭제되지 않는다**(코어 BlockNote에서도 재현되는 버그).\r\n *\r\n * 재구성 규칙:\r\n * - col만 차지하는 셀(colspan 1) → 제거.\r\n * - col을 가로질러 colspan>1로 걸친 셀 → colspan-1(및 colwidth 보정)로 유지.\r\n * - 그 외 셀 → 그대로 유지.\r\n * - 모든 원본 셀이 제거돼 **비게 되는 행**은 행 자체를 삭제하고, 그 행을 rowspan으로\r\n * 덮던 셀들의 rowspan을 줄인다.\r\n * 모든 셀/표 attr(병합, 배경색, verticalAlignment, rowHeight, tableAlignment 등)은\r\n * 노드를 그대로 재생성하므로 보존된다.\r\n *\r\n * @returns 적용할 transaction, 처리 불가/무변경 시 null.\r\n */\r\nfunction buildDeleteColumnTr(\r\n state: any,\r\n tablePos: number,\r\n col: number,\r\n rowPx: number[] | null,\r\n): any {\r\n const table = state.doc.nodeAt(tablePos);\r\n if (!table || table.type.name !== \"table\") return null;\r\n const map = TableMap.get(table);\r\n const W = map.width;\r\n const H = map.height;\r\n if (col < 0 || col >= W || W <= 1) return null;\r\n\r\n // 1. 고유 셀 + 원점(top,left)·span 수집.\r\n type CellInfo = {\r\n node: any;\r\n top: number;\r\n left: number;\r\n rowspan: number;\r\n colspan: number;\r\n };\r\n const cells: CellInfo[] = [];\r\n const seen = new Set<number>();\r\n for (let r = 0; r < H; r++) {\r\n for (let c = 0; c < W; c++) {\r\n const pos = map.map[r * W + c];\r\n if (seen.has(pos)) continue;\r\n seen.add(pos);\r\n const node = table.nodeAt(pos);\r\n if (!node) continue;\r\n cells.push({\r\n node,\r\n top: r,\r\n left: c,\r\n rowspan: node.attrs.rowspan ?? 1,\r\n colspan: node.attrs.colspan ?? 1,\r\n });\r\n }\r\n }\r\n\r\n // 2. 행별 \"살아남는 원점 셀 수\" 계산 → 0이면 그 행은 죽는다(삭제).\r\n const spansCol = (c: CellInfo) => c.left <= col && col < c.left + c.colspan;\r\n const isDropped = (c: CellInfo) => spansCol(c) && c.colspan === 1;\r\n const survivingByRow = new Array(H).fill(0);\r\n for (const c of cells) if (!isDropped(c)) survivingByRow[c.top]++;\r\n const rowDies = survivingByRow.map((n) => n === 0);\r\n const newRowIndex: number[] = [];\r\n let ni = 0;\r\n for (let r = 0; r < H; r++) newRowIndex[r] = rowDies[r] ? -1 : ni++;\r\n const newH = ni;\r\n if (newH === 0) return null; // 표 전체가 비는 경우는 처리하지 않음.\r\n\r\n // 3. 새 행 배열 구성(원점 셀을 left 순서대로 push).\r\n const newRows: any[][] = Array.from({ length: newH }, () => []);\r\n for (const c of cells) {\r\n if (isDropped(c)) continue;\r\n const newColspan = c.colspan - (spansCol(c) ? 1 : 0);\r\n let dyingWithin = 0;\r\n for (let r = c.top; r < c.top + c.rowspan; r++) if (rowDies[r]) dyingWithin++;\r\n const newRowspan = Math.max(1, c.rowspan - dyingWithin);\r\n\r\n const attrs: any = { ...c.node.attrs, colspan: newColspan, rowspan: newRowspan };\r\n if (spansCol(c) && Array.isArray(attrs.colwidth) && attrs.colwidth.length) {\r\n const cw = attrs.colwidth.slice();\r\n cw.splice(col - c.left, 1);\r\n attrs.colwidth = cw.some((w: number) => w > 0) ? cw : null;\r\n }\r\n // 높이 보존: rowspan이 줄어든(=빈 행이 제거된) 셀은 collapse로 키를 잃는다.\r\n // 원래 차지하던 행들의 렌더 높이 합을 rowHeight로 박아 시각 높이를 유지한다.\r\n if (dyingWithin > 0 && rowPx) {\r\n let sum = 0;\r\n let ok = true;\r\n for (let r = c.top; r < c.top + c.rowspan; r++) {\r\n if (typeof rowPx[r] !== \"number\") {\r\n ok = false;\r\n break;\r\n }\r\n sum += rowPx[r];\r\n }\r\n if (ok && sum > 0) attrs.rowHeight = Math.round(sum);\r\n }\r\n const target = newRowIndex[c.top];\r\n if (target >= 0) newRows[target].push(c.node.type.create(attrs, c.node.content));\r\n }\r\n\r\n // 4. 새 table 노드 생성 후 교체.\r\n const rowType = table.child(0)?.type;\r\n if (!rowType) return null;\r\n const rowNodes = newRows.map((rowCells) => rowType.create(null, rowCells));\r\n const newTable = table.type.create(table.attrs, rowNodes);\r\n\r\n const tr = state.tr;\r\n tr.replaceWith(tablePos, tablePos + table.nodeSize, newTable);\r\n return tr.docChanged ? tr : null;\r\n}\r\n\r\n/**\r\n * 행/열 삭제를 대상 표에서 직접(결정적으로) 수행한다.\r\n *\r\n * 배경(중요):\r\n * - 커스텀 포커스 기반 그립 메뉴가 열리면 ProseMirror 선택이 셀 밖으로 빠질 수 있어,\r\n * 코어 `tableHandles.removeRowOrColumn`(선택/`view.tablePos` 의존)이 실패한다.\r\n * - 인접 열에 rowspan 병합이 있으면 코어 deleteColumn이 첫 열을 못 지운다(위 참고).\r\n *\r\n * 삭제 대상 표는 (1) 컨트롤러가 노출한 포커스 표 id → (2) 선택 → (3) view.tablePos\r\n * 순으로 찾는다. 열 삭제는 위 재구성 방식(buildDeleteColumnTr)으로 병합 셀까지 안전하게\r\n * 처리하고, 행 삭제는 코어 deleteRow(행 전체를 한 번에 제거 → 빈-셀 문제 없음)를 쓴다.\r\n *\r\n * @returns 처리 성공 여부. 표를 못 찾으면 false(호출부가 코어 기본 동작으로 폴백).\r\n */\r\nexport function removeFocusedRowOrColumn(\r\n editor: any,\r\n index: number,\r\n direction: \"row\" | \"column\",\r\n): boolean {\r\n const tiptap = editor?._tiptapEditor;\r\n if (!tiptap) return false;\r\n const { state } = tiptap;\r\n\r\n // 1) 컨트롤러가 노출한 포커스 표 id(가장 권위 있음).\r\n let tablePos = -1;\r\n const activeId = editor?.__lumirActiveTableId;\r\n if (activeId) {\r\n const p = findTableNodePos(tiptap, activeId);\r\n if (p >= 0 && state.doc.nodeAt(p)?.type.name === \"table\") tablePos = p;\r\n }\r\n // 2) 현재 선택을 감싸는 table.\r\n if (tablePos < 0) {\r\n const $from = state.selection.$from;\r\n for (let d = $from.depth; d > 0; d--) {\r\n if ($from.node(d).type.name === \"table\") {\r\n tablePos = $from.before(d);\r\n break;\r\n }\r\n }\r\n }\r\n // 3) 코어 hover 추적 위치로 폴백.\r\n if (tablePos < 0) {\r\n const vp = editor?.tableHandles?.view?.tablePos;\r\n if (typeof vp === \"number\" && state.doc.nodeAt(vp)?.type.name === \"table\") {\r\n tablePos = vp;\r\n }\r\n }\r\n if (tablePos < 0) return false;\r\n\r\n try {\r\n if (direction === \"column\") {\r\n const rowPx = measureRowHeights(tiptap.view, tablePos);\r\n const tr = buildDeleteColumnTr(state, tablePos, index, rowPx);\r\n if (!tr) return false;\r\n tiptap.view.dispatch(tr);\r\n return true;\r\n }\r\n // 행 삭제: 대상 표 기준 CellSelection 구성 후 코어 deleteRow.\r\n const tableInside = state.doc.resolve(tablePos + 1);\r\n const rowStart = state.doc.resolve(tableInside.posAtIndex(index) + 1);\r\n const cellPos = state.doc.resolve(rowStart.posAtIndex(0));\r\n const selState = state.apply(\r\n state.tr.setSelection(new CellSelection(cellPos)),\r\n );\r\n return deleteRow(selState, (tr: any) => tiptap.view.dispatch(tr));\r\n } catch {\r\n return false;\r\n }\r\n}\r\n","/**\r\n * 엑셀/스프레드시트 표 붙여넣기 서식 정규화\r\n *\r\n * 엑셀은 셀 서식을 CSS 인라인 스타일(color/background/text-align 등)로 내보내지만,\r\n * BlockNote 테이블 셀은 자기 속성(data-text-color / data-background-color /\r\n * data-text-alignment)만 파싱하고 임의의 CSS 색은 무시한다.\r\n * 이 모듈은 엑셀 HTML을 BlockNote가 인식하는 형태로 변환한다:\r\n * - CSS color/background → 가장 가까운 BlockNote 팔레트 색 → data-* 속성\r\n * - text-align / align → data-text-alignment\r\n * - font-weight/font-style/underline → <strong>/<em>/<u> 래핑 (마크 보존 강화)\r\n *\r\n * BlockNote 색은 고정 팔레트(10색)뿐이라 임의 hex는 \"근사치\"로 양자화된다.\r\n */\r\n\r\ntype RGB = [number, number, number];\r\n\r\nconst NAMED_COLORS: Record<string, string> = {\r\n black: \"#000000\",\r\n white: \"#ffffff\",\r\n red: \"#ff0000\",\r\n green: \"#008000\",\r\n blue: \"#0000ff\",\r\n yellow: \"#ffff00\",\r\n orange: \"#ffa500\",\r\n purple: \"#800080\",\r\n gray: \"#808080\",\r\n grey: \"#808080\",\r\n};\r\n\r\n/** CSS 색 문자열(#hex, rgb(), 일부 named)을 RGB로 파싱. 실패 시 null */\r\nexport function parseCssColorToRgb(input?: string | null): RGB | null {\r\n if (!input) return null;\r\n const s = input.trim().toLowerCase();\r\n if (!s || s === \"transparent\" || s === \"none\" || s === \"inherit\") return null;\r\n\r\n let m = s.match(/^#([0-9a-f]{3}|[0-9a-f]{6})$/);\r\n if (m) {\r\n let h = m[1];\r\n if (h.length === 3)\r\n h = h\r\n .split(\"\")\r\n .map((c) => c + c)\r\n .join(\"\");\r\n return [\r\n parseInt(h.slice(0, 2), 16),\r\n parseInt(h.slice(2, 4), 16),\r\n parseInt(h.slice(4, 6), 16),\r\n ];\r\n }\r\n\r\n m = s.match(/^rgba?\\(([^)]+)\\)$/);\r\n if (m) {\r\n const parts = m[1].split(\",\").map((x) => parseFloat(x.trim()));\r\n if (parts.length >= 3 && parts.slice(0, 3).every((n) => !isNaN(n))) {\r\n return [parts[0], parts[1], parts[2]] as RGB;\r\n }\r\n return null;\r\n }\r\n\r\n if (NAMED_COLORS[s]) return parseCssColorToRgb(NAMED_COLORS[s]);\r\n return null;\r\n}\r\n\r\n/** RGB(0-255) → HSL. h:0-360, s:0-1, l:0-1 */\r\nfunction rgbToHsl([r, g, b]: RGB): [number, number, number] {\r\n r /= 255;\r\n g /= 255;\r\n b /= 255;\r\n const max = Math.max(r, g, b);\r\n const min = Math.min(r, g, b);\r\n const l = (max + min) / 2;\r\n let h = 0;\r\n let s = 0;\r\n const d = max - min;\r\n if (d !== 0) {\r\n s = l > 0.5 ? d / (2 - max - min) : d / (max + min);\r\n switch (max) {\r\n case r:\r\n h = ((g - b) / d + (g < b ? 6 : 0)) * 60;\r\n break;\r\n case g:\r\n h = ((b - r) / d + 2) * 60;\r\n break;\r\n default:\r\n h = ((r - g) / d + 4) * 60;\r\n break;\r\n }\r\n }\r\n return [h, s, l];\r\n}\r\n\r\n// 유채색 팔레트의 기준 색상(hue). TEXT_COLORS의 채도 높은 hex 기준.\r\nconst HUE_REFERENCE: { value: string; hue: number }[] = [\r\n { value: \"red\", hue: 0 },\r\n { value: \"brown\", hue: 17 },\r\n { value: \"orange\", hue: 30 },\r\n { value: \"yellow\", hue: 46 },\r\n { value: \"green\", hue: 150 },\r\n { value: \"blue\", hue: 197 },\r\n { value: \"purple\", hue: 262 },\r\n { value: \"pink\", hue: 324 },\r\n];\r\n\r\nfunction hueDist(a: number, b: number): number {\r\n const d = Math.abs(a - b) % 360;\r\n return d > 180 ? 360 - d : d;\r\n}\r\n\r\n/**\r\n * 임의 색 → BlockNote 팔레트 value (hue 기반 매칭).\r\n * 무채색(낮은 채도)은 검정류=default, 그 외 회색=gray.\r\n */\r\nfunction paletteValueFromRgb(rgb: RGB): string {\r\n const [h, s, l] = rgbToHsl(rgb);\r\n if (s < 0.15) {\r\n // 무채색\r\n if (l < 0.35) return \"default\"; // 검정류\r\n if (l > 0.85) return \"default\"; // 흰색류(무배경/기본 글자배경)\r\n return \"gray\";\r\n }\r\n let best = \"gray\";\r\n let bestDist = Infinity;\r\n for (const ref of HUE_REFERENCE) {\r\n const d = hueDist(h, ref.hue);\r\n if (d < bestDist) {\r\n bestDist = d;\r\n best = ref.value;\r\n }\r\n }\r\n return best;\r\n}\r\n\r\n/** 임의 색 → 가장 가까운 텍스트 팔레트 value (검정류는 default) */\r\nexport function nearestTextColorValue(rgb: RGB): string {\r\n return paletteValueFromRgb(rgb);\r\n}\r\n\r\n/** 임의 색 → 가장 가까운 배경 팔레트 value (흰색/근사 흰색은 default=무배경) */\r\nexport function nearestBackgroundColorValue(rgb: RGB): string {\r\n const [, s, l] = rgbToHsl(rgb);\r\n // 흰색/근사 흰색은 배경 없음으로 간주\r\n if (s < 0.12 && l > 0.85) return \"default\";\r\n return paletteValueFromRgb(rgb);\r\n}\r\n\r\n/** \"k:v; k2:v2\" → { k: v } */\r\nfunction parseStyle(style: string): Record<string, string> {\r\n const out: Record<string, string> = {};\r\n style.split(\";\").forEach((decl) => {\r\n const idx = decl.indexOf(\":\");\r\n if (idx === -1) return;\r\n const k = decl.slice(0, idx).trim().toLowerCase();\r\n const v = decl.slice(idx + 1).trim();\r\n if (k) out[k] = v;\r\n });\r\n return out;\r\n}\r\n\r\n/** background 단축 속성에서 색만 추출 시도 */\r\nfunction colorFromBackgroundShorthand(value: string): RGB | null {\r\n if (!value) return null;\r\n const direct = parseCssColorToRgb(value);\r\n if (direct) return direct;\r\n for (const token of value.split(/\\s+/)) {\r\n const rgb = parseCssColorToRgb(token);\r\n if (rgb) return rgb;\r\n }\r\n return null;\r\n}\r\n\r\n/** rgba alpha=0 / transparent 여부 */\r\nfunction isTransparentColor(css?: string | null): boolean {\r\n if (!css) return true;\r\n const s = css.trim().toLowerCase();\r\n if (s === \"transparent\" || s === \"none\") return true;\r\n const m = s.match(/^rgba?\\(([^)]+)\\)$/);\r\n if (m) {\r\n const p = m[1].split(\",\").map((x) => parseFloat(x.trim()));\r\n if (p.length >= 4 && p[3] === 0) return true;\r\n }\r\n return false;\r\n}\r\n\r\n/** CSS text-align 값을 BlockNote 정렬 value로 (left/start는 기본이라 빈 문자열) */\r\nfunction normalizeAlign(ta?: string | null): string {\r\n const v = (ta || \"\").trim().toLowerCase();\r\n if (v === \"right\" || v === \"end\") return \"right\";\r\n if (v === \"center\") return \"center\";\r\n if (v === \"justify\") return \"justify\";\r\n return \"\";\r\n}\r\n\r\n/** CSS vertical-align → BlockNote verticalAlignment (top/기본은 null로 스킵) */\r\nfunction mapVerticalAlign(v?: string | null): \"middle\" | \"bottom\" | null {\r\n const s = (v || \"\").trim().toLowerCase();\r\n if (s === \"middle\" || s === \"center\") return \"middle\";\r\n if (s === \"bottom\") return \"bottom\";\r\n return null;\r\n}\r\n\r\n/** font-size 문자열을 px로. computed는 px, inline은 pt/px. 실패 시 null. */\r\nfunction fontSizeToPx(raw?: string | null): number | null {\r\n if (!raw) return null;\r\n const m = String(raw).trim().match(/^([\\d.]+)\\s*(px|pt)?$/i);\r\n if (!m) return null;\r\n const v = parseFloat(m[1]);\r\n if (!Number.isFinite(v) || v <= 0) return null;\r\n return (m[2] || \"px\").toLowerCase() === \"pt\" ? v * (96 / 72) : v;\r\n}\r\n\r\ninterface CellFormat {\r\n bgRgb: RGB | null;\r\n bgTransparent: boolean;\r\n colorRgb: RGB | null;\r\n align: string;\r\n bold: boolean;\r\n italic: boolean;\r\n underline: boolean;\r\n /** 셀 글자 크기(px). 본문 기본(14px) 근처면 적용하지 않는다. */\r\n fontSizePx: number | null;\r\n /** 셀 세로 정렬(middle/bottom만; top/기본은 null). */\r\n verticalAlign: \"middle\" | \"bottom\" | null;\r\n}\r\n\r\n/** 셀 서식을 BlockNote data-* 속성/마크로 적용 */\r\nfunction applyCellFormatting(el: HTMLElement, fmt: CellFormat): void {\r\n if (fmt.bgRgb && !fmt.bgTransparent) {\r\n const v = nearestBackgroundColorValue(fmt.bgRgb);\r\n if (v !== \"default\") el.setAttribute(\"data-background-color\", v);\r\n }\r\n if (fmt.colorRgb) {\r\n const v = nearestTextColorValue(fmt.colorRgb);\r\n if (v !== \"default\") el.setAttribute(\"data-text-color\", v);\r\n }\r\n if ([\"right\", \"center\", \"justify\"].includes(fmt.align)) {\r\n el.setAttribute(\"data-text-alignment\", fmt.align);\r\n }\r\n if ((fmt.bold || fmt.italic || fmt.underline) && el.innerHTML.trim()) {\r\n let inner = el.innerHTML;\r\n if (fmt.underline) inner = `<u>${inner}</u>`;\r\n if (fmt.italic) inner = `<em>${inner}</em>`;\r\n if (fmt.bold) inner = `<strong>${inner}</strong>`;\r\n el.innerHTML = inner;\r\n }\r\n // 세로 정렬: VerticalAlignmentExtension parseHTML이 data-vertical-alignment를 읽는다.\r\n if (fmt.verticalAlign) {\r\n el.setAttribute(\"data-vertical-alignment\", fmt.verticalAlign);\r\n }\r\n // 글자 크기: 본문 기본(14px)과 1px 넘게 다르면 셀 내용을 FontSize 스타일 span으로 래핑.\r\n // (FontSize 스타일은 data-style-type/data-value 형태로 붙여넣기 왕복됨)\r\n if (\r\n fmt.fontSizePx &&\r\n Math.abs(fmt.fontSizePx - 14) > 1 &&\r\n el.innerHTML.trim()\r\n ) {\r\n const v = `${Math.round(fmt.fontSizePx)}px`;\r\n el.innerHTML =\r\n `<span data-style-type=\"fontSize\" data-value=\"${v}\" style=\"font-size:${v}\">` +\r\n el.innerHTML +\r\n `</span>`;\r\n }\r\n}\r\n\r\n/** getComputedStyle로 셀 서식 읽기 (클래스/<style>/inline 모두 반영) */\r\nfunction readComputedFormat(el: HTMLElement): CellFormat {\r\n const cs = getComputedStyle(el);\r\n const fw = cs.fontWeight;\r\n const fwNum = parseInt(fw, 10);\r\n const decoration = cs.textDecorationLine || cs.textDecoration || \"\";\r\n return {\r\n bgRgb: parseCssColorToRgb(cs.backgroundColor),\r\n bgTransparent: isTransparentColor(cs.backgroundColor),\r\n colorRgb: parseCssColorToRgb(cs.color),\r\n align: normalizeAlign(cs.textAlign),\r\n bold: fw === \"bold\" || fw === \"bolder\" || (!isNaN(fwNum) && fwNum >= 600),\r\n italic: (cs.fontStyle || \"\").toLowerCase().includes(\"italic\"),\r\n underline: decoration.toLowerCase().includes(\"underline\"),\r\n fontSizePx: fontSizeToPx(cs.fontSize),\r\n // ⚠️ computed vertical-align은 td 기본값이 \"middle\"이라 셀마다 잘못 붙는다.\r\n // 명시적 inline style / valign 속성만 읽는다(기본값 노이즈 방지).\r\n verticalAlign: mapVerticalAlign(\r\n el.style?.verticalAlign || el.getAttribute(\"valign\"),\r\n ),\r\n };\r\n}\r\n\r\n/** inline style 속성만으로 셀 서식 읽기 (computed 불가 환경 폴백) */\r\nfunction readInlineFormat(el: HTMLElement): CellFormat {\r\n const sm = parseStyle(el.getAttribute(\"style\") || \"\");\r\n const bgRaw = sm[\"background-color\"] || sm[\"background\"] || \"\";\r\n const bgRgb =\r\n colorFromBackgroundShorthand(bgRaw) ||\r\n parseCssColorToRgb(el.getAttribute(\"bgcolor\"));\r\n const fw = (sm[\"font-weight\"] || \"\").toLowerCase();\r\n const decoration = sm[\"text-decoration\"] || sm[\"text-decoration-line\"] || \"\";\r\n return {\r\n bgRgb,\r\n bgTransparent: !bgRaw && !el.getAttribute(\"bgcolor\"),\r\n colorRgb: parseCssColorToRgb(sm[\"color\"]),\r\n align: normalizeAlign(sm[\"text-align\"] || el.getAttribute(\"align\")),\r\n bold: fw === \"bold\" || fw === \"bolder\" || parseInt(fw, 10) >= 600,\r\n italic: (sm[\"font-style\"] || \"\").toLowerCase().includes(\"italic\"),\r\n underline: decoration.toLowerCase().includes(\"underline\"),\r\n fontSizePx: fontSizeToPx(sm[\"font-size\"]),\r\n verticalAlign: mapVerticalAlign(\r\n sm[\"vertical-align\"] || el.getAttribute(\"valign\"),\r\n ),\r\n };\r\n}\r\n\r\n/**\r\n * 엑셀 표 HTML을 BlockNote가 인식하는 서식으로 정규화한다.\r\n *\r\n * 엑셀(데스크톱)은 셀 서식을 인라인 style이 아니라 <style> 블록의 클래스\r\n * (.xlNN { background:#FFFF00 })로 내보내는 경우가 많다. 따라서 표를 격리된\r\n * Shadow DOM(오프스크린)에 잠깐 넣고 getComputedStyle로 최종 서식을 읽어\r\n * 인라인·클래스·mso 형식을 모두 일괄 처리한다. Shadow DOM이라 엑셀 <style>이\r\n * 페이지로 누출되지 않는다. 실패/비브라우저 환경에서는 inline 파싱으로 폴백.\r\n */\r\nexport function normalizeExcelTableHtml(html: string): string {\r\n if (!html || typeof DOMParser === \"undefined\") return html;\r\n try {\r\n const doc = new DOMParser().parseFromString(html, \"text/html\");\r\n doc.querySelectorAll(\"script\").forEach((s) => s.remove());\r\n if (!doc.querySelector(\"table\")) return html;\r\n\r\n // 우선 경로: 실제 DOM + getComputedStyle\r\n if (\r\n typeof document !== \"undefined\" &&\r\n document.body &&\r\n typeof HTMLElement !== \"undefined\" &&\r\n typeof HTMLElement.prototype.attachShadow === \"function\"\r\n ) {\r\n let host: HTMLElement | null = null;\r\n try {\r\n host = document.createElement(\"div\");\r\n host.setAttribute(\"aria-hidden\", \"true\");\r\n host.style.cssText =\r\n \"position:absolute;left:-99999px;top:0;width:0;height:0;overflow:hidden;opacity:0;pointer-events:none;font-size:14px\";\r\n const shadow = host.attachShadow({ mode: \"open\" });\r\n const styles = Array.from(doc.querySelectorAll(\"style\"))\r\n .map((s) => s.outerHTML)\r\n .join(\"\");\r\n shadow.innerHTML = styles + doc.body.innerHTML;\r\n document.body.appendChild(host);\r\n\r\n shadow.querySelectorAll(\"td, th\").forEach((node) => {\r\n applyCellFormatting(node as HTMLElement, readComputedFormat(node as HTMLElement));\r\n });\r\n\r\n const out = Array.from(shadow.querySelectorAll(\"table\"))\r\n .map((t) => t.outerHTML)\r\n .join(\"\");\r\n return out || html;\r\n } finally {\r\n if (host && host.parentNode) host.parentNode.removeChild(host);\r\n }\r\n }\r\n\r\n // 폴백 경로: inline style만 파싱\r\n doc.querySelectorAll(\"td, th\").forEach((node) => {\r\n applyCellFormatting(node as HTMLElement, readInlineFormat(node as HTMLElement));\r\n });\r\n return (\r\n Array.from(doc.querySelectorAll(\"table\"))\r\n .map((t) => t.outerHTML)\r\n .join(\"\") || html\r\n );\r\n } catch {\r\n return html;\r\n }\r\n}\r\n","/**\r\n * 붙여넣은 표를 **에디터 콘텐츠 너비에 맞춰** 각 열 비율을 유지한 채 축소한다.\r\n *\r\n * 배경: Word/docx 등에서 복사한 표는 열 너비(pt/px/%)가 에디터보다 넓어 가로 스크롤이\r\n * 생긴다. BlockNote 표는 열 너비를 블록 content의 `columnWidths` 배열로 모델링하며,\r\n * 붙여넣기 HTML 파서는 셀 width를 columnWidths로 읽지 않는다(붙여넣은 표는 기본폭).\r\n * 그래서 (1) 붙여넣기 전에 HTML에서 열별 원본 너비를 읽어 비율 유지로 maxWidth 이하로\r\n * 스케일하고, (2) 붙여넣은 뒤 새 표 블록의 `columnWidths`를 그 값으로 설정한다.\r\n * table-layout:fixed라 columnWidths 합이 에디터 너비 이하면 가로 스크롤이 없다.\r\n */\r\n\r\nconst MIN_COL_PX = 24;\r\n\r\nfunction toPx(raw: string | null | undefined, maxWidth: number): number | null {\r\n if (!raw) return null;\r\n const m = String(raw).trim().match(/^([\\d.]+)\\s*(pt|px|%)?$/i);\r\n if (!m) return null;\r\n const v = parseFloat(m[1]);\r\n if (!Number.isFinite(v) || v <= 0) return null;\r\n const unit = (m[2] || \"px\").toLowerCase();\r\n if (unit === \"pt\") return v * (96 / 72);\r\n if (unit === \"%\") return (v / 100) * maxWidth;\r\n return v;\r\n}\r\n\r\nfunction elWidthPx(el: Element, maxWidth: number): number | null {\r\n const styleW = (el as HTMLElement).style?.width;\r\n return toPx(styleW, maxWidth) ?? toPx(el.getAttribute(\"width\"), maxWidth);\r\n}\r\n\r\n/** 표의 열별 원본 너비(px)를 colgroup<col> 또는 첫 행 셀에서 읽는다. 못 구하면 null. */\r\nfunction readColumnWidths(table: HTMLTableElement, maxWidth: number): number[] | null {\r\n const colEls = table.querySelector(\"colgroup\")?.querySelectorAll(\"col\");\r\n if (colEls && colEls.length > 0) {\r\n const widths: number[] = [];\r\n let ok = true;\r\n colEls.forEach((c) => {\r\n const span = parseInt(c.getAttribute(\"span\") || \"1\", 10) || 1;\r\n const w = elWidthPx(c, maxWidth);\r\n if (w == null) ok = false;\r\n for (let i = 0; i < span; i++) widths.push(w ?? 0);\r\n });\r\n if (ok && widths.length > 0) return widths;\r\n }\r\n const firstRow = table.querySelector(\"tr\");\r\n if (!firstRow) return null;\r\n const widths: number[] = [];\r\n let ok = true;\r\n Array.from(firstRow.children).forEach((cell) => {\r\n if (cell.tagName !== \"TD\" && cell.tagName !== \"TH\") return;\r\n const span = parseInt(cell.getAttribute(\"colspan\") || \"1\", 10) || 1;\r\n const w = elWidthPx(cell, maxWidth);\r\n if (w == null) ok = false;\r\n const per = (w ?? 0) / span;\r\n for (let i = 0; i < span; i++) widths.push(per);\r\n });\r\n return ok && widths.length > 0 ? widths : null;\r\n}\r\n\r\n/** 원본 너비를 maxWidth 이하로 비율 유지 스케일한 px 배열. 넘지 않으면 원본 유지. */\r\nfunction fitWidths(widths: number[], maxWidth: number): number[] {\r\n const total = widths.reduce((a, b) => a + b, 0);\r\n if (total <= 0) return widths;\r\n const scale = total > maxWidth ? maxWidth / total : 1;\r\n return widths.map((w) => Math.max(MIN_COL_PX, Math.round(w * scale)));\r\n}\r\n\r\n/**\r\n * 붙여넣기 HTML의 각 `<table>`에 대해, 에디터 너비(maxWidth)에 맞춘 columnWidths 배열을\r\n * HTML 등장 순서로 반환한다. 너비를 못 읽은 표는 null.\r\n */\r\nexport function computeFittedColumnWidthsPerTable(\r\n html: string,\r\n maxWidth: number,\r\n): (number[] | null)[] {\r\n if (!html || typeof DOMParser === \"undefined\" || !(maxWidth > 0)) return [];\r\n let doc: Document;\r\n try {\r\n doc = new DOMParser().parseFromString(html, \"text/html\");\r\n } catch {\r\n return [];\r\n }\r\n return Array.from(doc.querySelectorAll(\"table\")).map((t) => {\r\n const widths = readColumnWidths(t as HTMLTableElement, maxWidth);\r\n return widths ? fitWidths(widths, maxWidth) : null;\r\n });\r\n}\r\n\r\n/** blocks(및 children)에서 table 블록을 등장 순서로 모은다. */\r\nexport function collectTableBlocks(blocks: any[]): any[] {\r\n const out: any[] = [];\r\n const walk = (bs: any[]) => {\r\n for (const b of bs) {\r\n if (b?.type === \"table\") out.push(b);\r\n if (b?.children?.length) walk(b.children);\r\n }\r\n };\r\n walk(blocks || []);\r\n return out;\r\n}\r\n\r\n/**\r\n * 붙여넣기 직후 호출: beforeIds에 없던 새 표 블록들에 perTable[i] columnWidths를 적용한다.\r\n * 열 개수가 맞을 때만 적용(불일치 시 건너뜀).\r\n */\r\nexport function applyFittedWidthsToNewTables(\r\n editor: any,\r\n beforeIds: Set<string>,\r\n perTable: (number[] | null)[],\r\n): void {\r\n if (!editor || perTable.length === 0) return;\r\n const newTables = collectTableBlocks(editor.document).filter(\r\n (b) => !beforeIds.has(b.id),\r\n );\r\n newTables.forEach((tb, i) => {\r\n const widths = perTable[i];\r\n const current = tb?.content?.columnWidths;\r\n if (\r\n widths &&\r\n Array.isArray(current) &&\r\n current.length === widths.length\r\n ) {\r\n try {\r\n editor.updateBlock(tb, {\r\n type: \"table\",\r\n content: { ...tb.content, columnWidths: widths },\r\n });\r\n } catch {\r\n /* 단일 표 실패가 전체 붙여넣기를 막지 않도록 무시 */\r\n }\r\n }\r\n });\r\n}\r\n"],"mappings":"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","names":["FontSizeButton","import_react","import_core","import_react","import_core","import_react","endpoint","import_react","import_jsx_runtime","tr","import_prosemirror_state","import_core","import_core","import_react","import_jsx_runtime","handleMouseMove","import_react","import_jsx_runtime","import_jsx_runtime","import_react","import_jsx_runtime","handleMouseDown","import_react","import_jsx_runtime","handleMouseDown","import_react","import_jsx_runtime","iconMap","titleMap","handleMouseDown","import_react","import_jsx_runtime","iconMap","titleMap","handleMouseDown","import_react","import_jsx_runtime","handleMouseDown","import_react","import_jsx_runtime","handleMouseDown","import_react","import_jsx_runtime","handleMouseDown","import_react","import_jsx_runtime","handleMouseDown","import_react","import_jsx_runtime","handleMouseDown","import_react","import_jsx_runtime","handleMouseDown","import_react","import_jsx_runtime","handleMouseDown","found","import_jsx_runtime","import_core","import_core","import_prosemirror_state","import_prosemirror_view","offset","import_prosemirror_state","import_prosemirror_view","import_prosemirror_tables","import_prosemirror_state","tr","import_core","import_prosemirror_state","import_prosemirror_view","import_core","import_prosemirror_tables","import_prosemirror_state","schema","import_react","import_core","import_react","import_jsx_runtime","import_react","import_jsx_runtime","icons","import_react","import_jsx_runtime","icons","tooltips","import_react","import_jsx_runtime","DEFAULT_LABEL","toLabel","FontSizeButton","import_core","import_react","import_jsx_runtime","import_jsx_runtime","FontSizeButton","import_react","import_jsx_runtime","import_react","import_react","import_jsx_runtime","th","cellEl","tbodyEl","isMeaningful","fs","import_prosemirror_tables","widths","ok","import_jsx_runtime","editor","BlockSideMenu"]}
|