@growgroup/visual-editor 0.1.0
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/LICENSE +21 -0
- package/NOTICE +52 -0
- package/README.md +143 -0
- package/dist/editor.css +2 -0
- package/package.json +94 -0
- package/src/components/DeckSlideRender.tsx +21 -0
- package/src/components/SaveNote.tsx +21 -0
- package/src/components/auth/AuthProvider.tsx +44 -0
- package/src/components/ui/accordion.tsx +57 -0
- package/src/components/ui/alert-dialog.tsx +141 -0
- package/src/components/ui/badge.tsx +36 -0
- package/src/components/ui/button.tsx +56 -0
- package/src/components/ui/card.tsx +76 -0
- package/src/components/ui/checkbox.tsx +30 -0
- package/src/components/ui/collapsible.tsx +11 -0
- package/src/components/ui/context-menu.tsx +200 -0
- package/src/components/ui/dialog.tsx +122 -0
- package/src/components/ui/dropdown-menu.tsx +201 -0
- package/src/components/ui/input.tsx +22 -0
- package/src/components/ui/label.tsx +26 -0
- package/src/components/ui/popover.tsx +33 -0
- package/src/components/ui/scroll-area.tsx +48 -0
- package/src/components/ui/scrubbable-label.tsx +95 -0
- package/src/components/ui/select.tsx +159 -0
- package/src/components/ui/separator.tsx +31 -0
- package/src/components/ui/slider.tsx +28 -0
- package/src/components/ui/switch.tsx +29 -0
- package/src/components/ui/tabs.tsx +55 -0
- package/src/components/ui/textarea.tsx +22 -0
- package/src/components/ui/tooltip.tsx +32 -0
- package/src/components/viewer/useDeck.ts +58 -0
- package/src/editor/EditorContext.tsx +632 -0
- package/src/editor/EditorToolbar.tsx +446 -0
- package/src/editor/FrontendVisualEditor.tsx +2595 -0
- package/src/editor/autosave.ts +34 -0
- package/src/editor/components/AiPromptPopover.tsx +378 -0
- package/src/editor/components/BreakpointGuides.tsx +323 -0
- package/src/editor/components/BreakpointSelector.tsx +180 -0
- package/src/editor/components/ComponentPropertyEditor.tsx +426 -0
- package/src/editor/components/CssEditorDialog.tsx +168 -0
- package/src/editor/components/EditorCanvas.tsx +987 -0
- package/src/editor/components/EditorContextMenu.tsx +515 -0
- package/src/editor/components/EditorFooter.tsx +81 -0
- package/src/editor/components/EditorHeader.tsx +524 -0
- package/src/editor/components/EditorLayerPanel.tsx +1197 -0
- package/src/editor/components/EditorPropertyPanel.tsx +2996 -0
- package/src/editor/components/EditorSidebar.tsx +226 -0
- package/src/editor/components/FigmaColorPicker.tsx +1577 -0
- package/src/editor/components/HtmlEditorDialog.tsx +71 -0
- package/src/editor/components/HtmlImportDialog.tsx +346 -0
- package/src/editor/components/JsEditorDialog.tsx +175 -0
- package/src/editor/components/LayerTreeItem.tsx +494 -0
- package/src/editor/components/MasterComponentEditor.tsx +846 -0
- package/src/editor/components/MediaLibraryDialog.tsx +447 -0
- package/src/editor/components/PageSettingsDialog.tsx +776 -0
- package/src/editor/components/VariablePicker.tsx +355 -0
- package/src/editor/components/VariablesDialog.tsx +564 -0
- package/src/editor/components/VariantEditor.tsx +687 -0
- package/src/editor/components/VariantMatrix.tsx +507 -0
- package/src/editor/components/component-panel/CategoryAccordion.tsx +139 -0
- package/src/editor/components/component-panel/ComponentGrid.tsx +51 -0
- package/src/editor/components/component-panel/ComponentItem.tsx +368 -0
- package/src/editor/components/component-panel/ComponentPanel.tsx +275 -0
- package/src/editor/components/component-panel/ComponentSearch.tsx +82 -0
- package/src/editor/components/component-panel/VariantList.tsx +260 -0
- package/src/editor/components/component-panel/index.ts +12 -0
- package/src/editor/components/index.ts +48 -0
- package/src/editor/components/multi-page/CanvasZoomControls.tsx +71 -0
- package/src/editor/components/multi-page/InfiniteCanvas.tsx +95 -0
- package/src/editor/components/multi-page/MultiPageCanvasView.tsx +1404 -0
- package/src/editor/components/multi-page/PageFrameOverlay.tsx +76 -0
- package/src/editor/components/multi-page/PageLabel.tsx +34 -0
- package/src/editor/components/multi-page/PageLivePreview.tsx +242 -0
- package/src/editor/components/multi-page/PageThumbnail.tsx +59 -0
- package/src/editor/components/multi-page/index.ts +7 -0
- package/src/editor/components/ppt/PptChrome.tsx +2508 -0
- package/src/editor/components/ppt/PptComments.tsx +589 -0
- package/src/editor/components/ppt/PptDesignProposals.tsx +275 -0
- package/src/editor/components/ppt/PptFormatPane.tsx +320 -0
- package/src/editor/components/ppt/PptNotes.tsx +246 -0
- package/src/editor/components/property-panel/AlignmentPanel.tsx +110 -0
- package/src/editor/components/property-panel/AutoLayoutPanel.tsx +615 -0
- package/src/editor/components/property-panel/BorderSection.tsx +104 -0
- package/src/editor/components/property-panel/CompactNumberInput.tsx +237 -0
- package/src/editor/components/property-panel/CompactSizeInput.tsx +148 -0
- package/src/editor/components/property-panel/EffectSection.tsx +157 -0
- package/src/editor/components/property-panel/FillSection.tsx +69 -0
- package/src/editor/components/property-panel/GoogleFontPicker.tsx +192 -0
- package/src/editor/components/property-panel/ImageSection.tsx +206 -0
- package/src/editor/components/property-panel/ImgSrcSection.tsx +125 -0
- package/src/editor/components/property-panel/InstanceOverrideSection.tsx +413 -0
- package/src/editor/components/property-panel/LayoutSection.tsx +105 -0
- package/src/editor/components/property-panel/LinkSection.tsx +103 -0
- package/src/editor/components/property-panel/PropertyVariablePicker.tsx +311 -0
- package/src/editor/components/property-panel/ScalePanel.tsx +212 -0
- package/src/editor/components/property-panel/TypographySection.tsx +230 -0
- package/src/editor/components/property-panel/UnitAwareNumberInput.tsx +238 -0
- package/src/editor/components/property-panel/VariableAwareColorInput.tsx +477 -0
- package/src/editor/components/property-panel/VariableAwareInput.tsx +606 -0
- package/src/editor/components/property-panel/VariableAwareSizeInput.tsx +588 -0
- package/src/editor/components/property-panel/VariableAwareUnitInput.tsx +546 -0
- package/src/editor/components/property-panel/index.ts +24 -0
- package/src/editor/components/property-panel/unit-utils.ts +261 -0
- package/src/editor/components/shell/EditorTopBar.tsx +392 -0
- package/src/editor/components/shell/LeftPanel.tsx +47 -0
- package/src/editor/components/shell/PagesPanel.tsx +118 -0
- package/src/editor/components/variables-panel/ColorValueCell.tsx +165 -0
- package/src/editor/components/variables-panel/TextValueCell.tsx +116 -0
- package/src/editor/components/variables-panel/VariablesPanel.tsx +518 -0
- package/src/editor/components/variables-panel/index.ts +7 -0
- package/src/editor/constants.ts +468 -0
- package/src/editor/contexts/EditorArtboardContext.tsx +229 -0
- package/src/editor/contexts/EditorComponentsContext.tsx +1320 -0
- package/src/editor/contexts/EditorDocumentContext.tsx +113 -0
- package/src/editor/contexts/EditorHistoryContext.tsx +516 -0
- package/src/editor/contexts/EditorRefsContext.tsx +204 -0
- package/src/editor/contexts/EditorSelectionContext.tsx +54 -0
- package/src/editor/contexts/EditorToolContext.tsx +58 -0
- package/src/editor/contexts/EditorUIStateContext.tsx +122 -0
- package/src/editor/contexts/EditorVariablesContext.tsx +397 -0
- package/src/editor/contexts/EditorViewContext.tsx +101 -0
- package/src/editor/contexts/MultiPageCanvasContext.tsx +498 -0
- package/src/editor/contexts/index.ts +17 -0
- package/src/editor/editor-skin.css +122 -0
- package/src/editor/hooks/index.ts +38 -0
- package/src/editor/hooks/useAiReplace.ts +397 -0
- package/src/editor/hooks/useAltMeasure.ts +67 -0
- package/src/editor/hooks/useBrowserZoomPrevention.ts +121 -0
- package/src/editor/hooks/useCanvasControls.ts +1222 -0
- package/src/editor/hooks/useComponentEditMode.ts +658 -0
- package/src/editor/hooks/useComponentInstances.ts +515 -0
- package/src/editor/hooks/useContextMenuHandler.ts +157 -0
- package/src/editor/hooks/useCoordinateTransform.ts +207 -0
- package/src/editor/hooks/useDragResize.ts +1317 -0
- package/src/editor/hooks/useDrawingMode.ts +472 -0
- package/src/editor/hooks/useEditorColors.ts +144 -0
- package/src/editor/hooks/useEditorMessages.ts +108 -0
- package/src/editor/hooks/useElementActions.ts +1754 -0
- package/src/editor/hooks/useElementSelection.ts +1573 -0
- package/src/editor/hooks/useFocusManagement.ts +249 -0
- package/src/editor/hooks/useGoogleFonts.ts +305 -0
- package/src/editor/hooks/useIframeInitializer.ts +584 -0
- package/src/editor/hooks/useIframeSetup.ts +360 -0
- package/src/editor/hooks/useImageUpload.ts +270 -0
- package/src/editor/hooks/useInfiniteCanvas.ts +301 -0
- package/src/editor/hooks/useKeyboardShortcuts.ts +205 -0
- package/src/editor/hooks/useMarqueeSelection.ts +388 -0
- package/src/editor/hooks/useMediaLibrary.ts +129 -0
- package/src/editor/hooks/usePageSettingsManager.ts +739 -0
- package/src/editor/hooks/useResizablePanel.ts +163 -0
- package/src/editor/hooks/useRichPaste.ts +255 -0
- package/src/editor/hooks/useTouchGestures.ts +197 -0
- package/src/editor/index.ts +27 -0
- package/src/editor/types/page-settings.ts +513 -0
- package/src/editor/types.ts +382 -0
- package/src/editor/utils/align-elements.ts +169 -0
- package/src/editor/utils/api-json.ts +21 -0
- package/src/editor/utils/aspect-lock.ts +50 -0
- package/src/editor/utils/component-renderer.ts +702 -0
- package/src/editor/utils/component-sync.ts +845 -0
- package/src/editor/utils/crop-mode.ts +265 -0
- package/src/editor/utils/dom-utils.ts +2238 -0
- package/src/editor/utils/element-effects.ts +218 -0
- package/src/editor/utils/eyedropper.ts +35 -0
- package/src/editor/utils/figma-export.ts +560 -0
- package/src/editor/utils/figma-kiwi-decoder.ts +722 -0
- package/src/editor/utils/figma-kiwi-encoder.ts +583 -0
- package/src/editor/utils/figma-paste.ts +2262 -0
- package/src/editor/utils/flex-reorder.ts +231 -0
- package/src/editor/utils/flex-utils.ts +1230 -0
- package/src/editor/utils/geometry.ts +232 -0
- package/src/editor/utils/grid-layout.ts +210 -0
- package/src/editor/utils/html-utils.ts +470 -0
- package/src/editor/utils/index.ts +11 -0
- package/src/editor/utils/ink-style.ts +57 -0
- package/src/editor/utils/inline-format.ts +236 -0
- package/src/editor/utils/measure-distance.ts +120 -0
- package/src/editor/utils/override-apply.ts +526 -0
- package/src/editor/utils/override-detection.ts +613 -0
- package/src/editor/utils/paste-processors.ts +609 -0
- package/src/editor/utils/paste-sanitizer.ts +244 -0
- package/src/editor/utils/restore-flow.ts +188 -0
- package/src/editor/utils/shape-library.ts +171 -0
- package/src/editor/utils/slide-root.ts +29 -0
- package/src/editor/utils/smart-guides.ts +218 -0
- package/src/editor/utils/style-utils.ts +685 -0
- package/src/editor/utils/table-edit.ts +151 -0
- package/src/editor/utils/tailwind-mappings.ts +778 -0
- package/src/editor/utils/tailwind-utils.ts +804 -0
- package/src/editor/utils/text-highlight.ts +216 -0
- package/src/editor/utils/theme-to-variables.ts +341 -0
- package/src/editor/utils/variant-resolver.ts +456 -0
- package/src/editor/utils/viewport-utils.ts +295 -0
- package/src/hooks/useEditorHistory.ts +195 -0
- package/src/hooks/useEditorShortcuts.ts +260 -0
- package/src/index.ts +40 -0
- package/src/io.ts +134 -0
- package/src/lib/agent/slide-agent/types.ts +547 -0
- package/src/lib/agent/website-agent/types.ts +805 -0
- package/src/lib/api/auth-fetch.ts +58 -0
- package/src/lib/deck.ts +69 -0
- package/src/lib/export.ts +47 -0
- package/src/lib/firebase/config.ts +29 -0
- package/src/lib/firebase/css-variables.ts +378 -0
- package/src/lib/firebase/editor-components.ts +677 -0
- package/src/lib/firebase/storage.ts +469 -0
- package/src/lib/utils.ts +6 -0
- package/src/styles/editor.css +52 -0
- package/src/styles/skin.css +122 -0
- package/src/types/css-variables.ts +645 -0
- package/src/types/editor-components.ts +944 -0
- package/src/types/editor.ts +420 -0
- package/src/types/page-master.ts +197 -0
- package/src/types/page.ts +419 -0
- package/src/types/slide.ts +402 -0
- package/src/types/website-theme.ts +788 -0
- package/src/types/website.ts +374 -0
- package/src/vendor/firebase-firestore.ts +13 -0
- package/src/vendor/firebase-functions.ts +17 -0
|
@@ -0,0 +1,470 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTML生成・クリーンアップ関連のユーティリティ関数
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { SLIDE_WIDTH, SLIDE_HEIGHT, WEBPAGE_WIDTH, WEBPAGE_MIN_HEIGHT } from '../constants';
|
|
6
|
+
import type { EditorMode } from '../EditorContext';
|
|
7
|
+
import { prepareHtmlForSave } from './viewport-utils';
|
|
8
|
+
import { classifySaveAndRevert } from './dom-utils';
|
|
9
|
+
|
|
10
|
+
// キャンバス背景色(Figmaライクなダークグレー)
|
|
11
|
+
const CANVAS_BG_COLOR = '#1a1a1a';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* HTMLコンテンツにTailwind CSSが読み込まれているかチェック
|
|
15
|
+
* @param content - HTMLコンテンツ
|
|
16
|
+
* @returns Tailwind CSSが読み込まれている場合はtrue
|
|
17
|
+
*/
|
|
18
|
+
export function hasTailwindCss(content: string): boolean {
|
|
19
|
+
// Tailwind CSS CDNリンクのパターン
|
|
20
|
+
const tailwindPatterns = [
|
|
21
|
+
/cdn\.tailwindcss\.com/i,
|
|
22
|
+
/@tailwindcss\/browser/i,
|
|
23
|
+
/tailwindcss-browser/i,
|
|
24
|
+
/tailwind\.min\.css/i,
|
|
25
|
+
/tailwind\.css/i,
|
|
26
|
+
/<style[^>]*>[\s\S]*@tailwind\s+(base|components|utilities)/i,
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
return tailwindPatterns.some(pattern => pattern.test(content));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* HTMLコンテンツが外部サイトからインポートされた可能性があるかチェック
|
|
34
|
+
* 外部CSSリンクや大量のインラインスタイルがある場合はtrue
|
|
35
|
+
* @param content - HTMLコンテンツ
|
|
36
|
+
* @returns 外部サイトからのインポートの可能性がある場合はtrue
|
|
37
|
+
*/
|
|
38
|
+
export function hasExternalStyles(content: string): boolean {
|
|
39
|
+
// 外部CSSリンクのパターン
|
|
40
|
+
const externalCssPatterns = [
|
|
41
|
+
/<link[^>]*rel=["']stylesheet["'][^>]*href=["']https?:\/\//i,
|
|
42
|
+
/<link[^>]*href=["']https?:\/\/[^>]*rel=["']stylesheet["']/i,
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
// 外部CSSリンクがある
|
|
46
|
+
if (externalCssPatterns.some(pattern => pattern.test(content))) {
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// 大量のインラインスタイル(<style>タグ内に100文字以上)
|
|
51
|
+
const styleTagMatch = content.match(/<style[^>]*>([\s\S]*?)<\/style>/gi);
|
|
52
|
+
if (styleTagMatch) {
|
|
53
|
+
const totalStyleLength = styleTagMatch.reduce((sum, tag) => {
|
|
54
|
+
const innerContent = tag.replace(/<\/?style[^>]*>/gi, '');
|
|
55
|
+
return sum + innerContent.length;
|
|
56
|
+
}, 0);
|
|
57
|
+
// 500文字以上のスタイルがあれば外部コンテンツの可能性が高い
|
|
58
|
+
if (totalStyleLength > 500) {
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Tailwind CSS Browser スクリプトの注入用HTML
|
|
68
|
+
* blob URLからの読み込みに対応するため、絶対URLを使用
|
|
69
|
+
*
|
|
70
|
+
* 重要: 既存のスタイルを壊さないよう、preflightを無効化
|
|
71
|
+
* - ユーティリティクラスのみを有効化
|
|
72
|
+
* - CSSリセットは適用しない
|
|
73
|
+
*
|
|
74
|
+
* @param safeMode - trueの場合、より安全なモード(preflightを完全に無効化)
|
|
75
|
+
*/
|
|
76
|
+
function getTailwindScriptHtml(safeMode: boolean = false): string {
|
|
77
|
+
// window.location.originを使用して絶対URLを構築
|
|
78
|
+
// SSR時はデフォルトのCDN URLにフォールバック
|
|
79
|
+
const baseUrl = typeof window !== 'undefined'
|
|
80
|
+
? window.location.origin
|
|
81
|
+
: '';
|
|
82
|
+
const scriptUrl = baseUrl
|
|
83
|
+
? `${baseUrl}/vendor/tailwindcss-browser.js`
|
|
84
|
+
: 'https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4';
|
|
85
|
+
|
|
86
|
+
// セーフモード: より厳格にpreflightを無効化し、既存スタイルへの影響を最小化
|
|
87
|
+
if (safeMode) {
|
|
88
|
+
return `
|
|
89
|
+
<!-- Tailwind CSS Browser (safe mode - utilities only) -->
|
|
90
|
+
<script src="${scriptUrl}" data-tailwind-injected="true"></script>
|
|
91
|
+
<style type="text/tailwindcss">
|
|
92
|
+
/* preflightを完全に無効化 - ユーティリティのみ使用 */
|
|
93
|
+
@layer base {
|
|
94
|
+
/* 空のbaseレイヤーでpreflightを上書き */
|
|
95
|
+
}
|
|
96
|
+
@theme {
|
|
97
|
+
/* 最小限のテーマ設定 */
|
|
98
|
+
--font-sans: system-ui, sans-serif;
|
|
99
|
+
${collectHostThemeTokens()}
|
|
100
|
+
}
|
|
101
|
+
</style>
|
|
102
|
+
<style>
|
|
103
|
+
/* 既存スタイルを保護: Tailwindのグローバルリセットを無効化 */
|
|
104
|
+
:where([data-tailwind-injected]) ~ * {
|
|
105
|
+
all: revert-layer;
|
|
106
|
+
}
|
|
107
|
+
</style>`;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// 通常モード
|
|
111
|
+
return `
|
|
112
|
+
<!-- Tailwind CSS Browser (self-hosted with CDN fallback) -->
|
|
113
|
+
<script src="${scriptUrl}" data-tailwind-injected="true"></script>
|
|
114
|
+
<style type="text/tailwindcss">
|
|
115
|
+
@layer base {
|
|
116
|
+
/* CSSリセットを最小化 */
|
|
117
|
+
}
|
|
118
|
+
@theme {
|
|
119
|
+
--font-sans: 'Noto Sans JP', system-ui, sans-serif;
|
|
120
|
+
${collectHostThemeTokens()}
|
|
121
|
+
}
|
|
122
|
+
</style>`;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* キャンバス+アートボード構造の編集用HTMLを生成
|
|
127
|
+
* @param content - HTMLコンテンツ
|
|
128
|
+
* @param editorMode - エディタモード('slide' または 'webpage')
|
|
129
|
+
*
|
|
130
|
+
* 構造:
|
|
131
|
+
* - html/body: 100%サイズ、キャンバス背景色
|
|
132
|
+
* - #canvas-container: ズーム/パン対象のスクロール領域
|
|
133
|
+
* - #artboard-wrapper: transform適用対象(ズーム/パン)
|
|
134
|
+
* - #artboard: 白背景、コンテンツ配置エリア
|
|
135
|
+
*/
|
|
136
|
+
export function generateEditableHtml(content: string, editorMode: EditorMode = 'slide'): string {
|
|
137
|
+
const artboardWidth = editorMode === 'webpage' ? WEBPAGE_WIDTH : SLIDE_WIDTH;
|
|
138
|
+
const artboardHeight = editorMode === 'webpage' ? WEBPAGE_MIN_HEIGHT : SLIDE_HEIGHT;
|
|
139
|
+
const artboardHeightStyle = editorMode === 'webpage' ? 'min-height' : 'height';
|
|
140
|
+
|
|
141
|
+
// コンテンツにTailwind CSSが含まれていない場合は注入
|
|
142
|
+
// 外部スタイルがある場合はセーフモードを使用(既存スタイルを保護)
|
|
143
|
+
const needsTailwind = !hasTailwindCss(content);
|
|
144
|
+
const useSafeMode = needsTailwind && hasExternalStyles(content);
|
|
145
|
+
const tailwindScript = needsTailwind ? getTailwindScriptHtml(useSafeMode) : '';
|
|
146
|
+
|
|
147
|
+
return `
|
|
148
|
+
<!DOCTYPE html>
|
|
149
|
+
<html lang="ja">
|
|
150
|
+
<head>
|
|
151
|
+
<meta charset="UTF-8">
|
|
152
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
153
|
+
${tailwindScript}
|
|
154
|
+
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
|
155
|
+
<link href="https://fonts.googleapis.com/icon?family=Material+Icons+Outlined" rel="stylesheet">
|
|
156
|
+
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@100;200;300;400;500;600;700;800;900&display=swap" rel="stylesheet">
|
|
157
|
+
<!-- スライドの欧文(font-en)。読み込まないと游ゴシックで代替され、
|
|
158
|
+
数字・フッターの幅がビューアと10px以上ずれる(検証で実測) -->
|
|
159
|
+
<link href="https://fonts.googleapis.com/css2?family=Jost:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
|
160
|
+
<style>
|
|
161
|
+
* { box-sizing: border-box; }
|
|
162
|
+
html, body {
|
|
163
|
+
margin: 0;
|
|
164
|
+
padding: 0;
|
|
165
|
+
width: 100%;
|
|
166
|
+
height: 100%;
|
|
167
|
+
overflow: hidden;
|
|
168
|
+
background-color: ${CANVAS_BG_COLOR};
|
|
169
|
+
/* iframe内のエディタUI(パンくず・ラベル等)用。
|
|
170
|
+
スライド本体は #artboard 内のルートが font-sans(游ゴシック)を持つので影響しない */
|
|
171
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Hiragino Sans', 'Noto Sans JP', Meiryo, sans-serif;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/* スクロール可能なキャンバスコンテナ */
|
|
175
|
+
#canvas-container {
|
|
176
|
+
position: absolute;
|
|
177
|
+
inset: 0;
|
|
178
|
+
overflow: auto;
|
|
179
|
+
background-color: ${CANVAS_BG_COLOR};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/* パディング用ラッパー(スクロール領域確保) */
|
|
183
|
+
#canvas-scroll-area {
|
|
184
|
+
display: flex;
|
|
185
|
+
align-items: center;
|
|
186
|
+
justify-content: center;
|
|
187
|
+
/* サイズはJSで動的に設定 */
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/* ズーム/パン適用対象 */
|
|
191
|
+
#artboard-wrapper {
|
|
192
|
+
flex-shrink: 0;
|
|
193
|
+
transform-origin: center center;
|
|
194
|
+
/* transform は JS で設定 */
|
|
195
|
+
/* 読み込み直後は非表示。倍率適用前の等倍(巨大)な一瞬を見せない。
|
|
196
|
+
EditorCanvas がロード時に倍率を当ててから可視化する */
|
|
197
|
+
visibility: hidden;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/* アートボード(コンテンツ領域) */
|
|
201
|
+
#artboard {
|
|
202
|
+
width: ${artboardWidth}px;
|
|
203
|
+
${artboardHeightStyle}: ${artboardHeight}px;
|
|
204
|
+
background-color: white;
|
|
205
|
+
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5);
|
|
206
|
+
position: relative;
|
|
207
|
+
overflow: ${editorMode === 'webpage' ? 'visible' : 'hidden'};
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/* ズーム中のスムーズな遷移を無効化(操作のレスポンス向上) */
|
|
211
|
+
#artboard-wrapper.zooming {
|
|
212
|
+
transition: none !important;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/* パンモード時のカーソル */
|
|
216
|
+
body.pan-mode {
|
|
217
|
+
cursor: grab !important;
|
|
218
|
+
}
|
|
219
|
+
body.pan-mode:active,
|
|
220
|
+
body.panning {
|
|
221
|
+
cursor: grabbing !important;
|
|
222
|
+
}
|
|
223
|
+
body.pan-mode #artboard,
|
|
224
|
+
body.pan-mode [data-editable="true"] {
|
|
225
|
+
pointer-events: none !important;
|
|
226
|
+
}
|
|
227
|
+
body.panning #artboard,
|
|
228
|
+
body.panning [data-editable="true"] {
|
|
229
|
+
pointer-events: none !important;
|
|
230
|
+
}
|
|
231
|
+
</style>
|
|
232
|
+
</head>
|
|
233
|
+
<body tabindex="-1">
|
|
234
|
+
<div id="canvas-container">
|
|
235
|
+
<div id="canvas-scroll-area">
|
|
236
|
+
<div id="artboard-wrapper">
|
|
237
|
+
<div id="artboard">
|
|
238
|
+
${content}
|
|
239
|
+
</div>
|
|
240
|
+
</div>
|
|
241
|
+
</div>
|
|
242
|
+
</div>
|
|
243
|
+
</body>
|
|
244
|
+
</html>`;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* クリーンなHTMLを取得(保存用)
|
|
249
|
+
* エディタ用の属性やクラスを削除
|
|
250
|
+
* 新しいキャンバス構造から#artboard内のコンテンツを抽出
|
|
251
|
+
* viewport単位(vw, vh等)を復元
|
|
252
|
+
*/
|
|
253
|
+
export function getCleanHtml(doc: Document): string {
|
|
254
|
+
// 新しい構造: #artboard からコンテンツを取得
|
|
255
|
+
const artboard = doc.getElementById('artboard');
|
|
256
|
+
const sourceElement = artboard || doc.body;
|
|
257
|
+
|
|
258
|
+
// viewport単位を復元したHTMLを取得
|
|
259
|
+
// prepareHtmlForSaveは内部でcloneを作成し、data-original-*から元の値を復元する
|
|
260
|
+
const viewportRestoredHtml = prepareHtmlForSave(sourceElement);
|
|
261
|
+
|
|
262
|
+
// 復元されたHTMLを再度パースしてクリーンアップ
|
|
263
|
+
const tempContainer = doc.createElement('div');
|
|
264
|
+
tempContainer.innerHTML = viewportRestoredHtml;
|
|
265
|
+
|
|
266
|
+
// 選択ボックスを削除
|
|
267
|
+
tempContainer.querySelectorAll('.selection-box').forEach(el => el.remove());
|
|
268
|
+
|
|
269
|
+
// マーキー選択ボックスを削除
|
|
270
|
+
tempContainer.querySelectorAll('.marquee-selection-box').forEach(el => el.remove());
|
|
271
|
+
|
|
272
|
+
// コメントの吹き出しレイヤー(表示専用)を削除
|
|
273
|
+
tempContainer.querySelectorAll('.gg-comment-layer').forEach(el => el.remove());
|
|
274
|
+
// Alt計測の赤い線(表示専用)も保存しない
|
|
275
|
+
tempContainer.querySelectorAll('#gg-measure-layer').forEach(el => el.remove());
|
|
276
|
+
// ドラッグ中の整列ガイド(表示専用)も保存しない
|
|
277
|
+
tempContainer.querySelectorAll('#gg-smart-guides').forEach(el => el.remove());
|
|
278
|
+
|
|
279
|
+
// トリミングモードのUI(枠・ハンドル)。確定/中断で消えるが、保険で除去
|
|
280
|
+
tempContainer.querySelectorAll('.gg-crop-ui').forEach(el => el.remove());
|
|
281
|
+
|
|
282
|
+
// 編集用属性を削除
|
|
283
|
+
tempContainer.querySelectorAll('[data-editable]').forEach(el => {
|
|
284
|
+
el.removeAttribute('contenteditable');
|
|
285
|
+
el.removeAttribute('data-editable');
|
|
286
|
+
el.removeAttribute('data-element-id');
|
|
287
|
+
el.removeAttribute('data-original-content');
|
|
288
|
+
el.removeAttribute('data-shape-type');
|
|
289
|
+
el.removeAttribute('data-inline');
|
|
290
|
+
el.classList.remove(
|
|
291
|
+
'selected', 'dragging', 'editing', 'rotating', 'panning',
|
|
292
|
+
'hover-preview', 'marquee-hover', 'marquee-active', 'text-editable-hover', 'drag-ghost',
|
|
293
|
+
);
|
|
294
|
+
if (!el.getAttribute('class')) el.removeAttribute('class');
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
// data-original-* 属性は prepareHtmlForSave で既に削除されているが、
|
|
298
|
+
// 念のため残っていれば削除
|
|
299
|
+
tempContainer.querySelectorAll('*').forEach(el => {
|
|
300
|
+
const attrs = Array.from(el.attributes);
|
|
301
|
+
attrs.forEach(attr => {
|
|
302
|
+
if (attr.name.startsWith('data-original-')) {
|
|
303
|
+
el.removeAttribute(attr.name);
|
|
304
|
+
}
|
|
305
|
+
});
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
// 触っていない要素の絶対位置変換を巻き戻し、触った要素に data-gg-dirty を付ける。
|
|
309
|
+
// これで保存HTMLは「原本のレイアウト + 実際の編集」だけになり、
|
|
310
|
+
// 原本TSXへの決定的な書き戻し(scripts/slide-writeback.mjs)が成立する
|
|
311
|
+
classifySaveAndRevert(tempContainer);
|
|
312
|
+
|
|
313
|
+
return tempContainer.innerHTML;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* HTMLコンテンツから編集可能要素にIDを付与
|
|
318
|
+
*/
|
|
319
|
+
export function addElementIds(html: string): string {
|
|
320
|
+
const parser = new DOMParser();
|
|
321
|
+
const doc = parser.parseFromString(html, 'text/html');
|
|
322
|
+
|
|
323
|
+
let idCounter = 0;
|
|
324
|
+
doc.querySelectorAll('[data-editable="true"]').forEach(el => {
|
|
325
|
+
if (!el.getAttribute('data-element-id')) {
|
|
326
|
+
el.setAttribute('data-element-id', `el-${Date.now()}-${idCounter++}`);
|
|
327
|
+
}
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
return doc.body.innerHTML;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* インポートされたHTMLに編集可能属性とIDを付与
|
|
335
|
+
* 直接の子要素にdata-editable="true"とdata-element-idを追加
|
|
336
|
+
* @param container - 親コンテナ要素(通常は#artboard)
|
|
337
|
+
*/
|
|
338
|
+
/**
|
|
339
|
+
* [移植時の追加] スライドのキャンバス本体かどうか。
|
|
340
|
+
* `#artboard` の直下にあり、ほぼ同じ大きさを占める器を指す。
|
|
341
|
+
* ここは「紙」であって編集対象の要素ではないので、選択・ドラッグさせない。
|
|
342
|
+
*/
|
|
343
|
+
function isSlideCanvas(el: HTMLElement, parent: HTMLElement): boolean {
|
|
344
|
+
if (parent.id !== 'artboard') return false;
|
|
345
|
+
if (el.parentElement !== parent) return false;
|
|
346
|
+
return el.offsetWidth >= parent.clientWidth - 2 && el.offsetHeight >= parent.clientHeight - 2;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
export function makeChildrenEditable(container: HTMLElement): number {
|
|
350
|
+
let count = 0;
|
|
351
|
+
const timestamp = Date.now();
|
|
352
|
+
|
|
353
|
+
// 直接の子要素をチェック
|
|
354
|
+
Array.from(container.children).forEach((child, index) => {
|
|
355
|
+
const el = child as HTMLElement;
|
|
356
|
+
|
|
357
|
+
// スキップする要素
|
|
358
|
+
if (
|
|
359
|
+
el.tagName === 'SCRIPT' ||
|
|
360
|
+
el.tagName === 'STYLE' ||
|
|
361
|
+
el.tagName === 'LINK' ||
|
|
362
|
+
el.classList?.contains('selection-box') ||
|
|
363
|
+
el.classList?.contains('marquee-selection-box') ||
|
|
364
|
+
// [移植時の修正] スライドのキャンバス自体(#artboard と同じ大きさの直下の器)は
|
|
365
|
+
// 編集対象にしない。これを選べてしまうと、掴んだ瞬間にスライドごと動いて
|
|
366
|
+
// 中身が版面の外(overflow:hidden)へ出るため、要素が消えたように見える
|
|
367
|
+
isSlideCanvas(el, container)
|
|
368
|
+
) {
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// data-editableがない場合は追加
|
|
373
|
+
if (!el.hasAttribute('data-editable')) {
|
|
374
|
+
el.setAttribute('data-editable', 'true');
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// data-element-idがない場合は追加
|
|
378
|
+
if (!el.hasAttribute('data-element-id')) {
|
|
379
|
+
el.setAttribute('data-element-id', `el-${timestamp}-${index}`);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
count++;
|
|
383
|
+
|
|
384
|
+
// 子孫要素も再帰的に処理(ネストされた編集可能要素のため)
|
|
385
|
+
count += makeDescendantsEditable(el, timestamp, index * 1000);
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
return count;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* 子孫要素に編集可能属性を付与(再帰処理)
|
|
393
|
+
* 特定の条件を満たす要素のみを編集可能にする
|
|
394
|
+
*/
|
|
395
|
+
function makeDescendantsEditable(parent: HTMLElement, timestamp: number, baseIndex: number): number {
|
|
396
|
+
let count = 0;
|
|
397
|
+
let localIndex = 0;
|
|
398
|
+
|
|
399
|
+
// 編集可能にすべき要素のタグ名
|
|
400
|
+
const EDITABLE_TAGS = new Set([
|
|
401
|
+
'DIV', 'SECTION', 'ARTICLE', 'HEADER', 'FOOTER', 'NAV', 'MAIN', 'ASIDE',
|
|
402
|
+
'FIGURE', 'FIGCAPTION', 'P', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6',
|
|
403
|
+
'UL', 'OL', 'LI', 'IMG', 'BUTTON', 'A', 'FORM', 'INPUT', 'TEXTAREA',
|
|
404
|
+
'TABLE', 'THEAD', 'TBODY', 'TR', 'TD', 'TH', 'SPAN', 'LABEL',
|
|
405
|
+
'VIDEO', 'AUDIO', 'CANVAS', 'SVG', 'BLOCKQUOTE', 'PRE', 'CODE',
|
|
406
|
+
]);
|
|
407
|
+
|
|
408
|
+
Array.from(parent.children).forEach((child) => {
|
|
409
|
+
const el = child as HTMLElement;
|
|
410
|
+
|
|
411
|
+
// スキップする要素
|
|
412
|
+
if (
|
|
413
|
+
el.tagName === 'SCRIPT' ||
|
|
414
|
+
el.tagName === 'STYLE' ||
|
|
415
|
+
el.tagName === 'LINK' ||
|
|
416
|
+
el.classList?.contains('selection-box') ||
|
|
417
|
+
el.classList?.contains('marquee-selection-box') ||
|
|
418
|
+
// [移植時の修正] スライドのキャンバス自体(#artboard と同じ大きさの直下の器)は
|
|
419
|
+
// 編集対象にしない。これを選べてしまうと、掴んだ瞬間にスライドごと動いて
|
|
420
|
+
// 中身が版面の外(overflow:hidden)へ出るため、要素が消えたように見える
|
|
421
|
+
isSlideCanvas(el, parent)
|
|
422
|
+
) {
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// 編集可能なタグかチェック
|
|
427
|
+
if (EDITABLE_TAGS.has(el.tagName)) {
|
|
428
|
+
// 既に属性がある場合はスキップ
|
|
429
|
+
if (!el.hasAttribute('data-editable')) {
|
|
430
|
+
el.setAttribute('data-editable', 'true');
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
if (!el.hasAttribute('data-element-id')) {
|
|
434
|
+
el.setAttribute('data-element-id', `el-${timestamp}-${baseIndex + localIndex}`);
|
|
435
|
+
localIndex++;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
count++;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// 再帰的に子要素を処理
|
|
442
|
+
count += makeDescendantsEditable(el, timestamp, baseIndex + localIndex * 100);
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
return count;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
/**
|
|
449
|
+
* [移植時の追加] ホストアプリの Tailwind v4 デザイントークンを iframe へ引き継ぐ。
|
|
450
|
+
* :root の CSS カスタムプロパティ(--color-* / --font-*)を computedStyle から読み取り、
|
|
451
|
+
* iframe 内 Tailwind Browser の @theme に流し込む。これにより
|
|
452
|
+
* bg-gg-green のようなプロジェクト固有クラスがエディタ内でも解決される。
|
|
453
|
+
* (Tailwind v4 は @layer 内にトークンを出力するため styleSheets 走査では取得できない)
|
|
454
|
+
*/
|
|
455
|
+
function collectHostThemeTokens(): string {
|
|
456
|
+
if (typeof window === 'undefined' || typeof document === 'undefined') return '';
|
|
457
|
+
try {
|
|
458
|
+
const cs = getComputedStyle(document.documentElement);
|
|
459
|
+
const lines: string[] = [];
|
|
460
|
+
for (const prop of Array.from(cs)) {
|
|
461
|
+
if (!prop.startsWith('--color-') && !prop.startsWith('--font-')) continue;
|
|
462
|
+
const value = cs.getPropertyValue(prop).trim();
|
|
463
|
+
if (!value) continue;
|
|
464
|
+
lines.push(` ${prop}: ${value};`);
|
|
465
|
+
}
|
|
466
|
+
return lines.join('\n');
|
|
467
|
+
} catch {
|
|
468
|
+
return '';
|
|
469
|
+
}
|
|
470
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* エディタユーティリティのエクスポート
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export * from './dom-utils';
|
|
6
|
+
export * from './style-utils';
|
|
7
|
+
export * from './override-detection';
|
|
8
|
+
export * from './override-apply';
|
|
9
|
+
export * from './component-renderer';
|
|
10
|
+
export * from './component-sync';
|
|
11
|
+
export * from './variant-resolver';
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ペン・鉛筆・蛍光ペンの「インク」設定。
|
|
3
|
+
*
|
|
4
|
+
* PowerPointの描画タブのペンギャラリー相当で、PPT風UIとFigma風UIの
|
|
5
|
+
* 両方から同じ設定を共有する。描画エンジン(useDrawingMode)は
|
|
6
|
+
* ストローク生成時にこのモジュール変数を参照するだけなので、
|
|
7
|
+
* どちらのUIから切り替えても次のストロークから反映される。
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export type InkPreset = {
|
|
11
|
+
id: string;
|
|
12
|
+
label: string;
|
|
13
|
+
color: string;
|
|
14
|
+
width: number;
|
|
15
|
+
opacity: number;
|
|
16
|
+
/** round=ペン/鉛筆, butt=蛍光ペン(マーカーの平筆感) */
|
|
17
|
+
cap: 'round' | 'butt';
|
|
18
|
+
/** エンジン側で使うツール(penはベジェ寄り、pencilはフリーハンド) */
|
|
19
|
+
tool: 'pen' | 'pencil';
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export const PEN_PRESETS: InkPreset[] = [
|
|
23
|
+
{ id: 'pen-black', label: 'ペン(黒)', color: '#1a1a1a', width: 3, opacity: 1, cap: 'round', tool: 'pen' },
|
|
24
|
+
{ id: 'pen-red', label: 'ペン(赤)', color: '#E03131', width: 3, opacity: 1, cap: 'round', tool: 'pen' },
|
|
25
|
+
{ id: 'pen-blue', label: 'ペン(青)', color: '#1971C2', width: 3, opacity: 1, cap: 'round', tool: 'pen' },
|
|
26
|
+
{ id: 'pencil-gray', label: '鉛筆', color: '#495057', width: 2, opacity: 0.9, cap: 'round', tool: 'pencil' },
|
|
27
|
+
{ id: 'hl-yellow', label: '蛍光ペン(黄)', color: '#FFD43B', width: 18, opacity: 0.45, cap: 'butt', tool: 'pencil' },
|
|
28
|
+
{ id: 'hl-green', label: '蛍光ペン(緑)', color: '#69DB7C', width: 18, opacity: 0.45, cap: 'butt', tool: 'pencil' },
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
export const inkStyle: {
|
|
32
|
+
presetId: string;
|
|
33
|
+
color: string;
|
|
34
|
+
width: number;
|
|
35
|
+
opacity: number;
|
|
36
|
+
cap: 'round' | 'butt';
|
|
37
|
+
} = {
|
|
38
|
+
presetId: 'pen-black',
|
|
39
|
+
color: '#1a1a1a',
|
|
40
|
+
width: 3,
|
|
41
|
+
opacity: 1,
|
|
42
|
+
cap: 'round',
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export function setInkPreset(p: InkPreset): void {
|
|
46
|
+
inkStyle.presetId = p.id;
|
|
47
|
+
inkStyle.color = p.color;
|
|
48
|
+
inkStyle.width = p.width;
|
|
49
|
+
inkStyle.opacity = p.opacity;
|
|
50
|
+
inkStyle.cap = p.cap;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** 任意色だけ差し替える(プリセットの太さは維持) */
|
|
54
|
+
export function setInkColor(color: string): void {
|
|
55
|
+
inkStyle.presetId = 'custom';
|
|
56
|
+
inkStyle.color = color;
|
|
57
|
+
}
|