@growgroup/visual-editor 0.4.1 → 0.6.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/README.md +47 -0
- package/dist/editor.css +1 -1
- package/package.json +7 -2
- package/src/editor/FrontendVisualEditor.tsx +71 -17
- package/src/editor/collab/CollabPresence.tsx +172 -0
- package/src/editor/collab/binding.ts +269 -0
- package/src/editor/collab/color.ts +28 -0
- package/src/editor/collab/connection.ts +110 -0
- package/src/editor/collab/layer.ts +168 -0
- package/src/editor/collab/merge.ts +112 -0
- package/src/editor/collab/morph.ts +113 -0
- package/src/editor/collab/room.ts +82 -0
- package/src/editor/collab/runtime.ts +442 -0
- package/src/editor/collab/signals.ts +11 -0
- package/src/editor/collab/store.ts +192 -0
- package/src/editor/collab/useCollab.ts +266 -0
- package/src/editor/components/AiPromptPopover.tsx +3 -2
- package/src/editor/components/EditorCanvas.tsx +11 -0
- package/src/editor/components/multi-page/MultiPageCanvasView.tsx +3 -0
- package/src/editor/components/ppt/PptComments.tsx +3 -8
- package/src/editor/components/shell/EditorTopBar.tsx +13 -1
- package/src/editor/components/shell/PagesPanel.tsx +3 -0
- package/src/editor/contexts/EditorAppearanceContext.tsx +5 -5
- package/src/editor/contexts/EditorHistoryContext.tsx +2 -0
- package/src/editor/contexts/MultiPageCanvasContext.tsx +14 -11
- package/src/editor/hooks/useGoogleFonts.ts +6 -8
- package/src/editor/hooks/useIframeSetup.ts +2 -0
- package/src/editor/hooks/useResizablePanel.ts +3 -2
- package/src/editor/utils/debug.ts +2 -5
- package/src/editor/utils/drop-target.ts +1 -1
- package/src/editor/utils/flex-reorder.ts +1 -1
- package/src/editor/utils/html-utils.ts +2 -0
- package/src/editor/utils/restore-flow.ts +1 -1
- package/src/editor/utils/smart-guides.ts +1 -1
- package/src/editor/utils/storage.ts +47 -0
- package/src/index.ts +15 -0
- package/src/io.ts +29 -0
- package/src/styles/skin.css +17 -0
- package/src/version.ts +1 -1
|
@@ -122,7 +122,7 @@ export function relaxContainerSize(container: HTMLElement): void {
|
|
|
122
122
|
/** 並べ替え・整列の対象にしない、エディタが描いている飾り */
|
|
123
123
|
const OVERLAY_SELECTOR =
|
|
124
124
|
'.selection-box,.marquee-selection-box,.resize-handle,.rotation-handle,' +
|
|
125
|
-
'.size-label,.element-breadcrumb,.gg-comment-layer,.gg-crop-ui,' +
|
|
125
|
+
'.size-label,.element-breadcrumb,.gg-comment-layer,.gg-collab-layer,.gg-crop-ui,' +
|
|
126
126
|
'#gg-measure-layer,#gg-smart-guides,#gg-reorder-indicator';
|
|
127
127
|
|
|
128
128
|
/** 実体のある直接の子だけを取り出す */
|
|
@@ -24,7 +24,7 @@ export const SNAP_THRESHOLD_SCREEN_PX = 4;
|
|
|
24
24
|
/** ガイドの候補にしないオーバーレイ類(エディタが描いている飾り) */
|
|
25
25
|
const OVERLAY_SELECTOR =
|
|
26
26
|
'.selection-box,.marquee-selection-box,.resize-handle,.rotation-handle,' +
|
|
27
|
-
'.size-label,.element-breadcrumb,.gg-comment-layer,.gg-crop-ui,' +
|
|
27
|
+
'.size-label,.element-breadcrumb,.gg-comment-layer,.gg-collab-layer,.gg-crop-ui,' +
|
|
28
28
|
'#gg-measure-layer,#gg-smart-guides,[data-drag-ghost],[data-flex-drop-indicator]';
|
|
29
29
|
|
|
30
30
|
/** 整列の候補座標。x/y ともに昇順ソート済み */
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ブラウザの記憶(localStorage / sessionStorage)への安全な出入口。
|
|
3
|
+
*
|
|
4
|
+
* 【なぜ関数越しにするのか】
|
|
5
|
+
* 共有ドロップに置く静的なエディタは `Content-Security-Policy: sandbox`(allow-same-origin 無し)で
|
|
6
|
+
* 配られる。この文脈は **opaque origin** で、`localStorage` は読むだけで SecurityError を投げる
|
|
7
|
+
* (`typeof localStorage` でも投げる ── プロパティの getter が走るため)。
|
|
8
|
+
* 「サイトデータをブロック」設定や、srcdoc / sandbox の紙面の中でも同じことが起きる。
|
|
9
|
+
*
|
|
10
|
+
* 記憶できるかどうかで挙動を変えないこと。ここは全部「読めなければ既定値、書けなければ黙って続行」。
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
function storage(kind: 'local' | 'session'): Storage | null {
|
|
14
|
+
try {
|
|
15
|
+
const s = kind === 'local' ? window.localStorage : window.sessionStorage;
|
|
16
|
+
return s ?? null;
|
|
17
|
+
} catch {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** 読む。使えない・入っていなければ null */
|
|
23
|
+
export function readStorage(key: string, kind: 'local' | 'session' = 'local'): string | null {
|
|
24
|
+
try {
|
|
25
|
+
return storage(kind)?.getItem(key) ?? null;
|
|
26
|
+
} catch {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** 書く。使えない・容量が一杯なら黙って諦める(表示や編集は続ける) */
|
|
32
|
+
export function writeStorage(key: string, value: string, kind: 'local' | 'session' = 'local'): void {
|
|
33
|
+
try {
|
|
34
|
+
storage(kind)?.setItem(key, value);
|
|
35
|
+
} catch {
|
|
36
|
+
/* 記憶できなくても動作は継続 */
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** 消す */
|
|
41
|
+
export function removeStorage(key: string, kind: 'local' | 'session' = 'local'): void {
|
|
42
|
+
try {
|
|
43
|
+
storage(kind)?.removeItem(key);
|
|
44
|
+
} catch {
|
|
45
|
+
/* 消せなくても動作は継続 */
|
|
46
|
+
}
|
|
47
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -60,7 +60,22 @@ export type {
|
|
|
60
60
|
EditorPartDef,
|
|
61
61
|
EditorPartCategory,
|
|
62
62
|
EditorPartsLibrary,
|
|
63
|
+
EditorCollab,
|
|
64
|
+
EditorCollabUser,
|
|
63
65
|
} from "./io";
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* 共同編集の居場所(io.collab を渡したときだけ中身が入る)。
|
|
69
|
+
* 利用側のページ一覧などに「誰がどのページに居るか」を出すのに使う
|
|
70
|
+
*/
|
|
71
|
+
export { useCollabPresence } from "./editor/collab/store";
|
|
72
|
+
export type { CollabPresence, CollabPeer, CollabStatus } from "./editor/collab/store";
|
|
73
|
+
/**
|
|
74
|
+
* 部屋名が中継(gg-manager の hocuspocus-server)に保存される形かの検査。
|
|
75
|
+
* 形を外れた部屋は中継はされるが保存されないので、殻・書き戻し役でも繋ぐ前にこれで確かめる
|
|
76
|
+
*/
|
|
77
|
+
export { checkCollabRoom } from "./editor/collab/room";
|
|
78
|
+
export type { CollabRoomCheck } from "./editor/collab/room";
|
|
64
79
|
export type { CSSVariableDefinition, CSSVariableCategory } from "./types/css-variables";
|
|
65
80
|
|
|
66
81
|
/**
|
package/src/io.ts
CHANGED
|
@@ -196,6 +196,35 @@ export type EditorIO = {
|
|
|
196
196
|
loadVariables?: () => Promise<CSSVariableDefinition[]>;
|
|
197
197
|
/** CSS 変数の保存。無ければブラウザ内(localStorage)に持つ */
|
|
198
198
|
saveVariables?: (variables: CSSVariableDefinition[]) => Promise<void>;
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* リアルタイム共同編集(Figma 風)。Hocuspocus の部屋に本文と居場所を載せる。
|
|
202
|
+
* 無ければ今までどおり(共同編集の UI も出さず、接続もしない)。
|
|
203
|
+
* あるページの部屋に入っている間は onSave の自動保存を呼ばない(ファイルに書くのは書き戻し役)
|
|
204
|
+
*/
|
|
205
|
+
collab?: EditorCollab;
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
/** 共同編集の参加者。color が無ければ id から決める */
|
|
209
|
+
export type EditorCollabUser = { id: string; name: string; color?: string };
|
|
210
|
+
|
|
211
|
+
export type EditorCollab = {
|
|
212
|
+
/** Hocuspocus の URL(wss://…) */
|
|
213
|
+
url: string;
|
|
214
|
+
/** contentId → ページの部屋名。null ならそのページは共同編集しない */
|
|
215
|
+
roomFor: (contentId: string) => string | null;
|
|
216
|
+
/** 案件の部屋名(居場所・pages) */
|
|
217
|
+
projectRoom: string;
|
|
218
|
+
/** contentId → route(案件の部屋の awareness と pages のキー) */
|
|
219
|
+
routeFor: (contentId: string) => string | null;
|
|
220
|
+
user: EditorCollabUser;
|
|
221
|
+
token?: () => Promise<string | undefined>;
|
|
222
|
+
/** getCleanHtml の結果 → 共有する本文。無ければそのまま */
|
|
223
|
+
encode?: (cleanHtml: string) => string;
|
|
224
|
+
/** 共有する本文 → エディタに渡す HTML。無ければそのまま */
|
|
225
|
+
decode?: (shared: string, contentId: string) => string;
|
|
226
|
+
/** true: 書き戻し役が居ないとき「変更は保存されません」を出し続ける(静的なエディタ) */
|
|
227
|
+
requireBridge?: boolean;
|
|
199
228
|
};
|
|
200
229
|
|
|
201
230
|
let current: EditorIO = {};
|
package/src/styles/skin.css
CHANGED
|
@@ -240,6 +240,23 @@
|
|
|
240
240
|
.gg-editor-ui .ed-save-status[data-save-status="dirty"] { color: var(--ed-warning); }
|
|
241
241
|
.gg-editor-ui .ed-save-status[data-save-status="error"] { color: var(--ed-danger); }
|
|
242
242
|
.gg-editor-ui .ed-save-status[data-save-status="saved"] svg { color: var(--ed-success); }
|
|
243
|
+
/* 共同編集(io.collab): 参加者のアバター・書き戻し先・接続状態・ページの点 */
|
|
244
|
+
.gg-editor-ui .ed-collab-presence { display: inline-flex; align-items: center; gap: 10px; }
|
|
245
|
+
.gg-editor-ui .ed-collab-avatars { display: inline-flex; align-items: center; padding-left: 4px; }
|
|
246
|
+
.gg-editor-ui .ed-collab-avatar { display: inline-flex; align-items: center; justify-content: center; width: 26px; height: 26px; margin-left: -4px; padding: 0; border-radius: 50%; border: 2px solid var(--ed-panel); color: #fff; font-size: 11px; font-weight: 700; line-height: 1; cursor: pointer; flex-shrink: 0; }
|
|
247
|
+
.gg-editor-ui .ed-collab-avatar:hover { transform: translateY(-1px); }
|
|
248
|
+
.gg-editor-ui .ed-collab-avatar-self { cursor: default; margin-left: 4px; box-shadow: 0 0 0 1px var(--ed-line-strong); }
|
|
249
|
+
.gg-editor-ui .ed-collab-avatar-self:hover, .gg-editor-ui .ed-collab-avatar-more:hover { transform: none; }
|
|
250
|
+
.gg-editor-ui .ed-collab-avatar-more { background: var(--ed-panel-raised); color: var(--ed-text); cursor: default; }
|
|
251
|
+
.gg-editor-ui .ed-collab-bridge { display: inline-flex; align-items: center; gap: 4px; font-size: 12px; white-space: nowrap; color: var(--ed-muted); }
|
|
252
|
+
.gg-editor-ui .ed-collab-bridge-warn { color: var(--ed-warning); }
|
|
253
|
+
.gg-editor-ui .ed-collab-bridge-alert { color: #fff; background: var(--ed-danger); padding: 3px 8px; border-radius: var(--ed-radius); font-weight: 600; }
|
|
254
|
+
.gg-editor-ui .ed-save-status[data-collab-status="synced"] svg { color: var(--ed-success); }
|
|
255
|
+
.gg-editor-ui .ed-save-status[data-collab-status="offline"] { color: var(--ed-danger); }
|
|
256
|
+
.gg-editor-ui .ed-save-status[data-collab-status="error"] { color: var(--ed-danger); font-weight: 600; }
|
|
257
|
+
.gg-editor-ui .ed-save-status[data-collab-pending] { color: var(--ed-warning); }
|
|
258
|
+
.gg-editor-ui .ed-collab-dots { display: inline-flex; align-items: center; gap: 2px; flex-shrink: 0; }
|
|
259
|
+
.gg-editor-ui .ed-collab-dot { width: 7px; height: 7px; border-radius: 50%; box-shadow: 0 0 0 1px var(--ed-panel); }
|
|
243
260
|
.gg-editor-ui .ed-panel-heading { height: 48px; display: flex; align-items: center; gap: 8px; padding: 0 16px; border-bottom: 1px solid var(--ed-line); flex-shrink: 0; font-size: 14px; font-weight: 600; }
|
|
244
261
|
.gg-editor-ui .ed-toolbar { border: 1px solid var(--ed-line); border-radius: 12px; padding: 8px; background: var(--ed-panel-raised); color: var(--ed-text); box-shadow: var(--ed-shadow); }
|
|
245
262
|
.gg-editor-ui .ed-footer { height: 36px; display: flex; align-items: center; justify-content: space-between; gap: 16px; padding: 0 16px; border-top: 1px solid var(--ed-line); background: var(--ed-panel); color: var(--ed-muted); font-size: 12px; flex-shrink: 0; }
|
package/src/version.ts
CHANGED