@upstart.gg/vite-plugins 0.0.37
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/dist/vite-plugin-upstart-attrs.d.ts +29 -0
- package/dist/vite-plugin-upstart-attrs.d.ts.map +1 -0
- package/dist/vite-plugin-upstart-attrs.js +323 -0
- package/dist/vite-plugin-upstart-attrs.js.map +1 -0
- package/dist/vite-plugin-upstart-editor/plugin.d.ts +15 -0
- package/dist/vite-plugin-upstart-editor/plugin.d.ts.map +1 -0
- package/dist/vite-plugin-upstart-editor/plugin.js +55 -0
- package/dist/vite-plugin-upstart-editor/plugin.js.map +1 -0
- package/dist/vite-plugin-upstart-editor/runtime/click-handler.d.ts +12 -0
- package/dist/vite-plugin-upstart-editor/runtime/click-handler.d.ts.map +1 -0
- package/dist/vite-plugin-upstart-editor/runtime/click-handler.js +57 -0
- package/dist/vite-plugin-upstart-editor/runtime/click-handler.js.map +1 -0
- package/dist/vite-plugin-upstart-editor/runtime/hover-overlay.d.ts +12 -0
- package/dist/vite-plugin-upstart-editor/runtime/hover-overlay.d.ts.map +1 -0
- package/dist/vite-plugin-upstart-editor/runtime/hover-overlay.js +91 -0
- package/dist/vite-plugin-upstart-editor/runtime/hover-overlay.js.map +1 -0
- package/dist/vite-plugin-upstart-editor/runtime/index.d.ts +22 -0
- package/dist/vite-plugin-upstart-editor/runtime/index.d.ts.map +1 -0
- package/dist/vite-plugin-upstart-editor/runtime/index.js +62 -0
- package/dist/vite-plugin-upstart-editor/runtime/index.js.map +1 -0
- package/dist/vite-plugin-upstart-editor/runtime/text-editor.d.ts +15 -0
- package/dist/vite-plugin-upstart-editor/runtime/text-editor.d.ts.map +1 -0
- package/dist/vite-plugin-upstart-editor/runtime/text-editor.js +292 -0
- package/dist/vite-plugin-upstart-editor/runtime/text-editor.js.map +1 -0
- package/dist/vite-plugin-upstart-editor/runtime/types.d.ts +126 -0
- package/dist/vite-plugin-upstart-editor/runtime/types.d.ts.map +1 -0
- package/dist/vite-plugin-upstart-editor/runtime/types.js +1 -0
- package/dist/vite-plugin-upstart-editor/runtime/utils.d.ts +15 -0
- package/dist/vite-plugin-upstart-editor/runtime/utils.d.ts.map +1 -0
- package/dist/vite-plugin-upstart-editor/runtime/utils.js +26 -0
- package/dist/vite-plugin-upstart-editor/runtime/utils.js.map +1 -0
- package/dist/vite-plugin-upstart-theme.d.ts +22 -0
- package/dist/vite-plugin-upstart-theme.d.ts.map +1 -0
- package/dist/vite-plugin-upstart-theme.js +179 -0
- package/dist/vite-plugin-upstart-theme.js.map +1 -0
- package/package.json +63 -0
- package/src/tests/fixtures/routes/default-layout.tsx +10 -0
- package/src/tests/fixtures/routes/dynamic-route.tsx +10 -0
- package/src/tests/fixtures/routes/missing-attributes.tsx +8 -0
- package/src/tests/fixtures/routes/missing-path.tsx +9 -0
- package/src/tests/fixtures/routes/valid-full.tsx +15 -0
- package/src/tests/fixtures/routes/valid-minimal.tsx +10 -0
- package/src/tests/fixtures/routes/with-comments.tsx +12 -0
- package/src/tests/fixtures/routes/with-nested-objects.tsx +15 -0
- package/src/tests/upstart-editor-api.test.ts +367 -0
- package/src/tests/vite-plugin-upstart-attrs.test.ts +1189 -0
- package/src/tests/vite-plugin-upstart-editor.test.ts +81 -0
- package/src/upstart-editor-api.ts +204 -0
- package/src/vite-plugin-upstart-attrs.ts +708 -0
- package/src/vite-plugin-upstart-editor/PLAN.md +1391 -0
- package/src/vite-plugin-upstart-editor/plugin.ts +73 -0
- package/src/vite-plugin-upstart-editor/runtime/click-handler.ts +80 -0
- package/src/vite-plugin-upstart-editor/runtime/hover-overlay.ts +135 -0
- package/src/vite-plugin-upstart-editor/runtime/index.ts +90 -0
- package/src/vite-plugin-upstart-editor/runtime/text-editor.ts +401 -0
- package/src/vite-plugin-upstart-editor/runtime/types.ts +120 -0
- package/src/vite-plugin-upstart-editor/runtime/utils.ts +34 -0
- package/src/vite-plugin-upstart-theme.ts +314 -0
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { sendToParent } from "./utils.js";
|
|
2
|
+
import { getCurrentMode } from "./index.js";
|
|
3
|
+
|
|
4
|
+
//#region src/vite-plugin-upstart-editor/runtime/hover-overlay.ts
|
|
5
|
+
let overlay = null;
|
|
6
|
+
let currentTarget = null;
|
|
7
|
+
let isInitialized = false;
|
|
8
|
+
let rafId = null;
|
|
9
|
+
/**
|
|
10
|
+
* Initialize hover overlay.
|
|
11
|
+
*/
|
|
12
|
+
function initHoverOverlay() {
|
|
13
|
+
if (typeof document === "undefined") return;
|
|
14
|
+
if (isInitialized) return;
|
|
15
|
+
console.log("[Upstart Editor] Initializing hover overlay...");
|
|
16
|
+
document.addEventListener("mouseover", handleMouseOver);
|
|
17
|
+
document.addEventListener("mouseout", handleMouseOut);
|
|
18
|
+
window.addEventListener("scroll", scheduleOverlayUpdate, { passive: true });
|
|
19
|
+
window.addEventListener("resize", scheduleOverlayUpdate, { passive: true });
|
|
20
|
+
isInitialized = true;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Hide all overlays.
|
|
24
|
+
*/
|
|
25
|
+
function hideOverlays() {
|
|
26
|
+
if (overlay) {
|
|
27
|
+
overlay.style.display = "none";
|
|
28
|
+
currentTarget = null;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function handleMouseOver(event) {
|
|
32
|
+
if (getCurrentMode() !== "edit") return;
|
|
33
|
+
const target = event.target;
|
|
34
|
+
if (!target) return;
|
|
35
|
+
const component = target.closest("[data-upstart-component]");
|
|
36
|
+
if (!component) return;
|
|
37
|
+
if (!overlay) createOverlay();
|
|
38
|
+
currentTarget = component;
|
|
39
|
+
positionOverlay(component);
|
|
40
|
+
const hash = component.dataset.upstartHash;
|
|
41
|
+
if (hash) {
|
|
42
|
+
const rect = component.getBoundingClientRect();
|
|
43
|
+
sendToParent({
|
|
44
|
+
type: "element-hovered",
|
|
45
|
+
hash,
|
|
46
|
+
bounds: {
|
|
47
|
+
top: rect.top,
|
|
48
|
+
left: rect.left,
|
|
49
|
+
width: rect.width,
|
|
50
|
+
height: rect.height,
|
|
51
|
+
right: rect.right,
|
|
52
|
+
bottom: rect.bottom
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function handleMouseOut(event) {
|
|
58
|
+
const target = event.target;
|
|
59
|
+
const relatedTarget = event.relatedTarget;
|
|
60
|
+
if (!target) return;
|
|
61
|
+
const component = target.closest("[data-upstart-component]");
|
|
62
|
+
if (!component) return;
|
|
63
|
+
if (relatedTarget && component.contains(relatedTarget)) return;
|
|
64
|
+
hideOverlays();
|
|
65
|
+
}
|
|
66
|
+
function createOverlay() {
|
|
67
|
+
overlay = document.createElement("div");
|
|
68
|
+
overlay.id = "upstart-hover-overlay";
|
|
69
|
+
overlay.style.cssText = "position: absolute; pointer-events: none; border: 2px solid #3b82f6; background: rgba(59, 130, 246, 0.05); border-radius: 4px; z-index: 9999; transition: all 0.1s ease; display: none;";
|
|
70
|
+
document.body.appendChild(overlay);
|
|
71
|
+
}
|
|
72
|
+
function positionOverlay(element) {
|
|
73
|
+
if (!overlay) return;
|
|
74
|
+
const rect = element.getBoundingClientRect();
|
|
75
|
+
overlay.style.top = `${rect.top + window.scrollY}px`;
|
|
76
|
+
overlay.style.left = `${rect.left + window.scrollX}px`;
|
|
77
|
+
overlay.style.width = `${rect.width}px`;
|
|
78
|
+
overlay.style.height = `${rect.height}px`;
|
|
79
|
+
overlay.style.display = "block";
|
|
80
|
+
}
|
|
81
|
+
function scheduleOverlayUpdate() {
|
|
82
|
+
if (rafId !== null) return;
|
|
83
|
+
rafId = requestAnimationFrame(() => {
|
|
84
|
+
rafId = null;
|
|
85
|
+
if (currentTarget && overlay && overlay.style.display === "block") positionOverlay(currentTarget);
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
//#endregion
|
|
90
|
+
export { hideOverlays, initHoverOverlay };
|
|
91
|
+
//# sourceMappingURL=hover-overlay.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hover-overlay.js","names":["overlay: HTMLDivElement | null","currentTarget: HTMLElement | null","rafId: number | null"],"sources":["../../../src/vite-plugin-upstart-editor/runtime/hover-overlay.ts"],"sourcesContent":["import { getCurrentMode } from \"./index.js\";\nimport { sendToParent } from \"./utils.js\";\n\nlet overlay: HTMLDivElement | null = null;\nlet currentTarget: HTMLElement | null = null;\nlet isInitialized = false;\nlet rafId: number | null = null;\n\n/**\n * Initialize hover overlay.\n */\nexport function initHoverOverlay(): void {\n if (typeof document === \"undefined\") {\n return;\n }\n\n if (isInitialized) {\n return;\n }\n\n console.log(\"[Upstart Editor] Initializing hover overlay...\");\n\n document.addEventListener(\"mouseover\", handleMouseOver);\n document.addEventListener(\"mouseout\", handleMouseOut);\n window.addEventListener(\"scroll\", scheduleOverlayUpdate, { passive: true });\n window.addEventListener(\"resize\", scheduleOverlayUpdate, { passive: true });\n\n isInitialized = true;\n}\n\n/**\n * Hide all overlays.\n */\nexport function hideOverlays(): void {\n if (overlay) {\n overlay.style.display = \"none\";\n currentTarget = null;\n }\n}\n\nfunction handleMouseOver(event: MouseEvent): void {\n if (getCurrentMode() !== \"edit\") {\n return;\n }\n\n const target = event.target as HTMLElement | null;\n if (!target) {\n return;\n }\n\n const component = target.closest<HTMLElement>(\"[data-upstart-component]\");\n if (!component) {\n return;\n }\n\n if (!overlay) {\n createOverlay();\n }\n\n currentTarget = component;\n positionOverlay(component);\n\n const hash = component.dataset.upstartHash;\n if (hash) {\n const rect = component.getBoundingClientRect();\n sendToParent({\n type: \"element-hovered\",\n hash,\n bounds: {\n top: rect.top,\n left: rect.left,\n width: rect.width,\n height: rect.height,\n right: rect.right,\n bottom: rect.bottom,\n },\n });\n }\n}\n\nfunction handleMouseOut(event: MouseEvent): void {\n const target = event.target as HTMLElement | null;\n const relatedTarget = event.relatedTarget as HTMLElement | null;\n\n if (!target) {\n return;\n }\n\n const component = target.closest<HTMLElement>(\"[data-upstart-component]\");\n if (!component) {\n return;\n }\n\n if (relatedTarget && component.contains(relatedTarget)) {\n return;\n }\n\n hideOverlays();\n}\n\nfunction createOverlay(): void {\n overlay = document.createElement(\"div\");\n overlay.id = \"upstart-hover-overlay\";\n overlay.style.cssText =\n \"position: absolute; pointer-events: none; border: 2px solid #3b82f6; \" +\n \"background: rgba(59, 130, 246, 0.05); border-radius: 4px; z-index: 9999; \" +\n \"transition: all 0.1s ease; display: none;\";\n document.body.appendChild(overlay);\n}\n\nfunction positionOverlay(element: HTMLElement): void {\n if (!overlay) {\n return;\n }\n\n const rect = element.getBoundingClientRect();\n overlay.style.top = `${rect.top + window.scrollY}px`;\n overlay.style.left = `${rect.left + window.scrollX}px`;\n overlay.style.width = `${rect.width}px`;\n overlay.style.height = `${rect.height}px`;\n overlay.style.display = \"block\";\n}\n\nfunction scheduleOverlayUpdate(): void {\n if (rafId !== null) {\n return;\n }\n\n rafId = requestAnimationFrame(() => {\n rafId = null;\n if (currentTarget && overlay && overlay.style.display === \"block\") {\n positionOverlay(currentTarget);\n }\n });\n}\n"],"mappings":";;;;AAGA,IAAIA,UAAiC;AACrC,IAAIC,gBAAoC;AACxC,IAAI,gBAAgB;AACpB,IAAIC,QAAuB;;;;AAK3B,SAAgB,mBAAyB;AACvC,KAAI,OAAO,aAAa,YACtB;AAGF,KAAI,cACF;AAGF,SAAQ,IAAI,iDAAiD;AAE7D,UAAS,iBAAiB,aAAa,gBAAgB;AACvD,UAAS,iBAAiB,YAAY,eAAe;AACrD,QAAO,iBAAiB,UAAU,uBAAuB,EAAE,SAAS,MAAM,CAAC;AAC3E,QAAO,iBAAiB,UAAU,uBAAuB,EAAE,SAAS,MAAM,CAAC;AAE3E,iBAAgB;;;;;AAMlB,SAAgB,eAAqB;AACnC,KAAI,SAAS;AACX,UAAQ,MAAM,UAAU;AACxB,kBAAgB;;;AAIpB,SAAS,gBAAgB,OAAyB;AAChD,KAAI,gBAAgB,KAAK,OACvB;CAGF,MAAM,SAAS,MAAM;AACrB,KAAI,CAAC,OACH;CAGF,MAAM,YAAY,OAAO,QAAqB,2BAA2B;AACzE,KAAI,CAAC,UACH;AAGF,KAAI,CAAC,QACH,gBAAe;AAGjB,iBAAgB;AAChB,iBAAgB,UAAU;CAE1B,MAAM,OAAO,UAAU,QAAQ;AAC/B,KAAI,MAAM;EACR,MAAM,OAAO,UAAU,uBAAuB;AAC9C,eAAa;GACX,MAAM;GACN;GACA,QAAQ;IACN,KAAK,KAAK;IACV,MAAM,KAAK;IACX,OAAO,KAAK;IACZ,QAAQ,KAAK;IACb,OAAO,KAAK;IACZ,QAAQ,KAAK;IACd;GACF,CAAC;;;AAIN,SAAS,eAAe,OAAyB;CAC/C,MAAM,SAAS,MAAM;CACrB,MAAM,gBAAgB,MAAM;AAE5B,KAAI,CAAC,OACH;CAGF,MAAM,YAAY,OAAO,QAAqB,2BAA2B;AACzE,KAAI,CAAC,UACH;AAGF,KAAI,iBAAiB,UAAU,SAAS,cAAc,CACpD;AAGF,eAAc;;AAGhB,SAAS,gBAAsB;AAC7B,WAAU,SAAS,cAAc,MAAM;AACvC,SAAQ,KAAK;AACb,SAAQ,MAAM,UACZ;AAGF,UAAS,KAAK,YAAY,QAAQ;;AAGpC,SAAS,gBAAgB,SAA4B;AACnD,KAAI,CAAC,QACH;CAGF,MAAM,OAAO,QAAQ,uBAAuB;AAC5C,SAAQ,MAAM,MAAM,GAAG,KAAK,MAAM,OAAO,QAAQ;AACjD,SAAQ,MAAM,OAAO,GAAG,KAAK,OAAO,OAAO,QAAQ;AACnD,SAAQ,MAAM,QAAQ,GAAG,KAAK,MAAM;AACpC,SAAQ,MAAM,SAAS,GAAG,KAAK,OAAO;AACtC,SAAQ,MAAM,UAAU;;AAG1B,SAAS,wBAA8B;AACrC,KAAI,UAAU,KACZ;AAGF,SAAQ,4BAA4B;AAClC,UAAQ;AACR,MAAI,iBAAiB,WAAW,QAAQ,MAAM,YAAY,QACxD,iBAAgB,cAAc;GAEhC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { EditorMessage, EditorMode, UpstartEditorMessage } from "./types.js";
|
|
2
|
+
import { initClickHandler } from "./click-handler.js";
|
|
3
|
+
import { initHoverOverlay } from "./hover-overlay.js";
|
|
4
|
+
import { initTextEditor } from "./text-editor.js";
|
|
5
|
+
import { sendToParent } from "./utils.js";
|
|
6
|
+
|
|
7
|
+
//#region src/vite-plugin-upstart-editor/runtime/index.d.ts
|
|
8
|
+
/**
|
|
9
|
+
* Get the current editor mode.
|
|
10
|
+
*/
|
|
11
|
+
declare function getCurrentMode(): EditorMode;
|
|
12
|
+
/**
|
|
13
|
+
* Set the current editor mode.
|
|
14
|
+
*/
|
|
15
|
+
declare function setMode(mode: EditorMode): void;
|
|
16
|
+
/**
|
|
17
|
+
* Initialize the Upstart editor runtime.
|
|
18
|
+
*/
|
|
19
|
+
declare function initUpstartEditor(): void;
|
|
20
|
+
//#endregion
|
|
21
|
+
export { type EditorMessage, type UpstartEditorMessage, getCurrentMode, initClickHandler, initHoverOverlay, initTextEditor, initUpstartEditor, sendToParent, setMode };
|
|
22
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../../../src/vite-plugin-upstart-editor/runtime/index.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;iBAYgB,cAAA,CAAA,GAAkB;;;AAAlC;AAOgB,iBAAA,OAAA,CAAc,IAAU,EAAV,UAAU,CAAA,EAAA,IAAA;AAaxC;;;iBAAgB,iBAAA,CAAA"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { sendToParent } from "./utils.js";
|
|
2
|
+
import { initClickHandler } from "./click-handler.js";
|
|
3
|
+
import { hideOverlays, initHoverOverlay } from "./hover-overlay.js";
|
|
4
|
+
import { destroyAllActiveEditors, initTextEditor } from "./text-editor.js";
|
|
5
|
+
|
|
6
|
+
//#region src/vite-plugin-upstart-editor/runtime/index.ts
|
|
7
|
+
let currentMode = "preview";
|
|
8
|
+
let isInitialized = false;
|
|
9
|
+
/**
|
|
10
|
+
* Get the current editor mode.
|
|
11
|
+
*/
|
|
12
|
+
function getCurrentMode() {
|
|
13
|
+
return currentMode;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Set the current editor mode.
|
|
17
|
+
*/
|
|
18
|
+
function setMode(mode) {
|
|
19
|
+
currentMode = mode;
|
|
20
|
+
if (mode === "edit") enableEditMode();
|
|
21
|
+
else disableEditMode();
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Initialize the Upstart editor runtime.
|
|
25
|
+
*/
|
|
26
|
+
function initUpstartEditor() {
|
|
27
|
+
if (typeof window === "undefined") return;
|
|
28
|
+
if (isInitialized) return;
|
|
29
|
+
try {
|
|
30
|
+
console.log("[Upstart Editor] Initializing...");
|
|
31
|
+
currentMode = "preview";
|
|
32
|
+
isInitialized = true;
|
|
33
|
+
window.addEventListener("message", handleParentMessage);
|
|
34
|
+
initTextEditor();
|
|
35
|
+
initClickHandler();
|
|
36
|
+
initHoverOverlay();
|
|
37
|
+
sendToParent({ type: "editor-ready" });
|
|
38
|
+
} catch (error) {
|
|
39
|
+
console.error("[Upstart Editor] Initialization failed:", error);
|
|
40
|
+
sendToParent({
|
|
41
|
+
type: "editor-error",
|
|
42
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function handleParentMessage(event) {
|
|
47
|
+
const message = event.data;
|
|
48
|
+
if (!message || message.source !== "upstart-editor-parent") return;
|
|
49
|
+
if (message.type === "set-mode") setMode(message.mode);
|
|
50
|
+
}
|
|
51
|
+
function enableEditMode() {
|
|
52
|
+
console.log("[Upstart Editor] Edit mode enabled");
|
|
53
|
+
}
|
|
54
|
+
function disableEditMode() {
|
|
55
|
+
console.log("[Upstart Editor] Preview mode enabled");
|
|
56
|
+
destroyAllActiveEditors();
|
|
57
|
+
hideOverlays();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
//#endregion
|
|
61
|
+
export { getCurrentMode, initClickHandler, initHoverOverlay, initTextEditor, initUpstartEditor, sendToParent, setMode };
|
|
62
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":["currentMode: EditorMode"],"sources":["../../../src/vite-plugin-upstart-editor/runtime/index.ts"],"sourcesContent":["import { initClickHandler } from \"./click-handler.js\";\nimport { initHoverOverlay, hideOverlays } from \"./hover-overlay.js\";\nimport { initTextEditor, destroyAllActiveEditors } from \"./text-editor.js\";\nimport { sendToParent } from \"./utils.js\";\nimport type { EditorMode, UpstartParentMessage } from \"./types.js\";\n\nlet currentMode: EditorMode = \"preview\";\nlet isInitialized = false;\n\n/**\n * Get the current editor mode.\n */\nexport function getCurrentMode(): EditorMode {\n return currentMode;\n}\n\n/**\n * Set the current editor mode.\n */\nexport function setMode(mode: EditorMode): void {\n currentMode = mode;\n\n if (mode === \"edit\") {\n enableEditMode();\n } else {\n disableEditMode();\n }\n}\n\n/**\n * Initialize the Upstart editor runtime.\n */\nexport function initUpstartEditor(): void {\n if (typeof window === \"undefined\") {\n return;\n }\n\n if (isInitialized) {\n return;\n }\n\n try {\n console.log(\"[Upstart Editor] Initializing...\");\n\n currentMode = \"preview\";\n isInitialized = true;\n\n window.addEventListener(\"message\", handleParentMessage);\n\n initTextEditor();\n initClickHandler();\n initHoverOverlay();\n\n sendToParent({ type: \"editor-ready\" });\n } catch (error) {\n console.error(\"[Upstart Editor] Initialization failed:\", error);\n sendToParent({\n type: \"editor-error\",\n error: error instanceof Error ? error.message : \"Unknown error\",\n });\n }\n}\n\nfunction handleParentMessage(event: MessageEvent): void {\n const message = event.data as UpstartParentMessage | undefined;\n\n if (!message || message.source !== \"upstart-editor-parent\") {\n return;\n }\n\n if (message.type === \"set-mode\") {\n setMode(message.mode);\n }\n}\n\nfunction enableEditMode(): void {\n console.log(\"[Upstart Editor] Edit mode enabled\");\n}\n\nfunction disableEditMode(): void {\n console.log(\"[Upstart Editor] Preview mode enabled\");\n destroyAllActiveEditors();\n hideOverlays();\n}\n\nexport { initTextEditor } from \"./text-editor.js\";\nexport { initClickHandler } from \"./click-handler.js\";\nexport { initHoverOverlay } from \"./hover-overlay.js\";\nexport { sendToParent } from \"./utils.js\";\nexport type { EditorMessage, UpstartEditorMessage } from \"./types.js\";\n"],"mappings":";;;;;;AAMA,IAAIA,cAA0B;AAC9B,IAAI,gBAAgB;;;;AAKpB,SAAgB,iBAA6B;AAC3C,QAAO;;;;;AAMT,SAAgB,QAAQ,MAAwB;AAC9C,eAAc;AAEd,KAAI,SAAS,OACX,iBAAgB;KAEhB,kBAAiB;;;;;AAOrB,SAAgB,oBAA0B;AACxC,KAAI,OAAO,WAAW,YACpB;AAGF,KAAI,cACF;AAGF,KAAI;AACF,UAAQ,IAAI,mCAAmC;AAE/C,gBAAc;AACd,kBAAgB;AAEhB,SAAO,iBAAiB,WAAW,oBAAoB;AAEvD,kBAAgB;AAChB,oBAAkB;AAClB,oBAAkB;AAElB,eAAa,EAAE,MAAM,gBAAgB,CAAC;UAC/B,OAAO;AACd,UAAQ,MAAM,2CAA2C,MAAM;AAC/D,eAAa;GACX,MAAM;GACN,OAAO,iBAAiB,QAAQ,MAAM,UAAU;GACjD,CAAC;;;AAIN,SAAS,oBAAoB,OAA2B;CACtD,MAAM,UAAU,MAAM;AAEtB,KAAI,CAAC,WAAW,QAAQ,WAAW,wBACjC;AAGF,KAAI,QAAQ,SAAS,WACnB,SAAQ,QAAQ,KAAK;;AAIzB,SAAS,iBAAuB;AAC9B,SAAQ,IAAI,qCAAqC;;AAGnD,SAAS,kBAAwB;AAC/B,SAAQ,IAAI,wCAAwC;AACpD,0BAAyB;AACzB,eAAc"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { UpstartEditorOptions } from "./types.js";
|
|
2
|
+
|
|
3
|
+
//#region src/vite-plugin-upstart-editor/runtime/text-editor.d.ts
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Initialize TipTap text editing for elements marked as editable.
|
|
7
|
+
*/
|
|
8
|
+
declare function initTextEditor(options?: UpstartEditorOptions): void;
|
|
9
|
+
/**
|
|
10
|
+
* Destroy all active editors.
|
|
11
|
+
*/
|
|
12
|
+
declare function destroyAllActiveEditors(): void;
|
|
13
|
+
//#endregion
|
|
14
|
+
export { destroyAllActiveEditors, initTextEditor };
|
|
15
|
+
//# sourceMappingURL=text-editor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"text-editor.d.ts","names":[],"sources":["../../../src/vite-plugin-upstart-editor/runtime/text-editor.ts"],"sourcesContent":[],"mappings":";;;;;;AA6BA;AA+BgB,iBA/BA,cAAA,CA+BuB,OAAA,CAAA,EA/BC,oBA+BD,CAAA,EAAA,IAAA;;;;iBAAvB,uBAAA,CAAA"}
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
import { sendToParent } from "./utils.js";
|
|
2
|
+
import { getCurrentMode } from "./index.js";
|
|
3
|
+
import { Editor } from "@tiptap/core";
|
|
4
|
+
import { BubbleMenu } from "@tiptap/extension-bubble-menu";
|
|
5
|
+
import Placeholder from "@tiptap/extension-placeholder";
|
|
6
|
+
import StarterKit from "@tiptap/starter-kit";
|
|
7
|
+
|
|
8
|
+
//#region src/vite-plugin-upstart-editor/runtime/text-editor.ts
|
|
9
|
+
const DEFAULT_OPTIONS = {
|
|
10
|
+
richTextElements: [
|
|
11
|
+
"p",
|
|
12
|
+
"div",
|
|
13
|
+
"article",
|
|
14
|
+
"section"
|
|
15
|
+
],
|
|
16
|
+
plainTextElements: [
|
|
17
|
+
"button",
|
|
18
|
+
"a",
|
|
19
|
+
"span",
|
|
20
|
+
"h1",
|
|
21
|
+
"h2",
|
|
22
|
+
"h3",
|
|
23
|
+
"h4",
|
|
24
|
+
"h5",
|
|
25
|
+
"h6",
|
|
26
|
+
"label"
|
|
27
|
+
],
|
|
28
|
+
bubbleMenu: true,
|
|
29
|
+
placeholder: "Start typing...",
|
|
30
|
+
autoSaveDelay: 1e3
|
|
31
|
+
};
|
|
32
|
+
const activeEditors = /* @__PURE__ */ new Map();
|
|
33
|
+
const saveTimeouts = /* @__PURE__ */ new Map();
|
|
34
|
+
const styleCache = /* @__PURE__ */ new WeakMap();
|
|
35
|
+
let isInitialized = false;
|
|
36
|
+
let shortcutsRegistered = false;
|
|
37
|
+
/**
|
|
38
|
+
* Initialize TipTap text editing for elements marked as editable.
|
|
39
|
+
*/
|
|
40
|
+
function initTextEditor(options = {}) {
|
|
41
|
+
if (typeof window === "undefined") return;
|
|
42
|
+
if (isInitialized) return;
|
|
43
|
+
try {
|
|
44
|
+
const resolvedOptions = {
|
|
45
|
+
...DEFAULT_OPTIONS,
|
|
46
|
+
...options
|
|
47
|
+
};
|
|
48
|
+
document.querySelectorAll("[data-upstart-editable-text=\"true\"]").forEach((element) => setupEditableElement(element, resolvedOptions));
|
|
49
|
+
registerKeyboardShortcuts();
|
|
50
|
+
isInitialized = true;
|
|
51
|
+
console.log("[Upstart Editor] Text editor initialized");
|
|
52
|
+
} catch (error) {
|
|
53
|
+
console.error("[Upstart Editor] Failed to initialize text editor:", error);
|
|
54
|
+
sendToParent({
|
|
55
|
+
type: "editor-error",
|
|
56
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Destroy all active editors.
|
|
62
|
+
*/
|
|
63
|
+
function destroyAllActiveEditors() {
|
|
64
|
+
for (const hash of activeEditors.keys()) destroyEditor(hash);
|
|
65
|
+
}
|
|
66
|
+
function setupEditableElement(element, options) {
|
|
67
|
+
const hash = getEditableHash(element);
|
|
68
|
+
if (!hash) return;
|
|
69
|
+
cacheStyles(element);
|
|
70
|
+
element.style.transition = "outline 0.15s ease";
|
|
71
|
+
element.addEventListener("dblclick", (event) => {
|
|
72
|
+
if (getCurrentMode() !== "edit") return;
|
|
73
|
+
event.preventDefault();
|
|
74
|
+
event.stopPropagation();
|
|
75
|
+
if (activeEditors.has(hash)) return;
|
|
76
|
+
activateEditor(element, hash, options);
|
|
77
|
+
});
|
|
78
|
+
element.addEventListener("mouseenter", () => {
|
|
79
|
+
if (getCurrentMode() !== "edit") return;
|
|
80
|
+
if (!activeEditors.has(hash)) applyHoverStyles(element);
|
|
81
|
+
});
|
|
82
|
+
element.addEventListener("mouseleave", () => {
|
|
83
|
+
if (!activeEditors.has(hash)) restoreStyles(element);
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
function activateEditor(element, hash, options) {
|
|
87
|
+
const editor = shouldUseRichText(element, options) ? createRichTextEditor(element, hash, options) : createPlainTextEditor(element, hash, options);
|
|
88
|
+
applyActiveStyles(element);
|
|
89
|
+
activeEditors.set(hash, {
|
|
90
|
+
editor,
|
|
91
|
+
element,
|
|
92
|
+
hash
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
function createPlainTextEditor(element, hash, options) {
|
|
96
|
+
return new Editor({
|
|
97
|
+
element,
|
|
98
|
+
extensions: [StarterKit.configure({
|
|
99
|
+
heading: false,
|
|
100
|
+
bold: false,
|
|
101
|
+
italic: false,
|
|
102
|
+
strike: false,
|
|
103
|
+
blockquote: false,
|
|
104
|
+
bulletList: false,
|
|
105
|
+
orderedList: false,
|
|
106
|
+
listItem: false,
|
|
107
|
+
codeBlock: false,
|
|
108
|
+
horizontalRule: false
|
|
109
|
+
}), Placeholder.configure({ placeholder: "Click to edit..." })],
|
|
110
|
+
content: element.textContent ?? "",
|
|
111
|
+
editorProps: { attributes: {
|
|
112
|
+
class: "upstart-editor-active",
|
|
113
|
+
style: "outline: 2px solid #3b82f6; outline-offset: 2px;"
|
|
114
|
+
} },
|
|
115
|
+
onUpdate: ({ editor }) => {
|
|
116
|
+
debouncedSave(hash, editor.getText(), options.autoSaveDelay);
|
|
117
|
+
},
|
|
118
|
+
onBlur: ({ editor }) => {
|
|
119
|
+
saveText(hash, editor.getText());
|
|
120
|
+
destroyEditor(hash);
|
|
121
|
+
},
|
|
122
|
+
onCreate: ({ editor }) => {
|
|
123
|
+
editor.commands.focus("end");
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
function createRichTextEditor(element, hash, options) {
|
|
128
|
+
const bubbleMenuElement = createBubbleMenuElement();
|
|
129
|
+
const extensions = [StarterKit, Placeholder.configure({ placeholder: options.placeholder })];
|
|
130
|
+
if (options.bubbleMenu) extensions.push(BubbleMenu.configure({ element: bubbleMenuElement }));
|
|
131
|
+
return new Editor({
|
|
132
|
+
element,
|
|
133
|
+
extensions,
|
|
134
|
+
content: element.innerHTML,
|
|
135
|
+
editorProps: { attributes: {
|
|
136
|
+
class: "upstart-editor-active",
|
|
137
|
+
style: "outline: 2px solid #3b82f6; outline-offset: 2px;"
|
|
138
|
+
} },
|
|
139
|
+
onUpdate: ({ editor: updatedEditor }) => {
|
|
140
|
+
debouncedSave(hash, updatedEditor.getHTML(), options.autoSaveDelay);
|
|
141
|
+
},
|
|
142
|
+
onBlur: ({ editor: updatedEditor }) => {
|
|
143
|
+
saveText(hash, updatedEditor.getHTML());
|
|
144
|
+
destroyEditor(hash);
|
|
145
|
+
},
|
|
146
|
+
onCreate: ({ editor: createdEditor }) => {
|
|
147
|
+
if (options.bubbleMenu) wireBubbleMenu(bubbleMenuElement, createdEditor);
|
|
148
|
+
createdEditor.commands.focus("end");
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
function shouldUseRichText(element, options) {
|
|
153
|
+
const tagName = element.tagName.toLowerCase();
|
|
154
|
+
if (options.plainTextElements.includes(tagName)) return false;
|
|
155
|
+
if (options.richTextElements.includes(tagName)) return true;
|
|
156
|
+
return true;
|
|
157
|
+
}
|
|
158
|
+
function debouncedSave(hash, content, delay) {
|
|
159
|
+
const existingTimeout = saveTimeouts.get(hash);
|
|
160
|
+
if (existingTimeout) clearTimeout(existingTimeout);
|
|
161
|
+
const timeoutId = window.setTimeout(() => {
|
|
162
|
+
sendToParent({
|
|
163
|
+
type: "text-update",
|
|
164
|
+
hash,
|
|
165
|
+
newText: content
|
|
166
|
+
});
|
|
167
|
+
}, delay);
|
|
168
|
+
saveTimeouts.set(hash, timeoutId);
|
|
169
|
+
}
|
|
170
|
+
function saveText(hash, newText) {
|
|
171
|
+
try {
|
|
172
|
+
sendToParent({
|
|
173
|
+
type: "text-save",
|
|
174
|
+
hash,
|
|
175
|
+
newText
|
|
176
|
+
});
|
|
177
|
+
console.log("[Upstart Editor] Text save message sent:", hash);
|
|
178
|
+
} catch (error) {
|
|
179
|
+
console.error("[Upstart Editor] Failed to send save message:", error);
|
|
180
|
+
sendToParent({
|
|
181
|
+
type: "editor-error",
|
|
182
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
function destroyEditor(hash) {
|
|
187
|
+
const instance = activeEditors.get(hash);
|
|
188
|
+
if (!instance) return;
|
|
189
|
+
instance.editor.destroy();
|
|
190
|
+
restoreStyles(instance.element);
|
|
191
|
+
activeEditors.delete(hash);
|
|
192
|
+
const existingTimeout = saveTimeouts.get(hash);
|
|
193
|
+
if (existingTimeout) {
|
|
194
|
+
clearTimeout(existingTimeout);
|
|
195
|
+
saveTimeouts.delete(hash);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
function registerKeyboardShortcuts() {
|
|
199
|
+
if (shortcutsRegistered) return;
|
|
200
|
+
document.addEventListener("keydown", (event) => {
|
|
201
|
+
if (event.key === "Escape") blurAllEditors();
|
|
202
|
+
if ((event.metaKey || event.ctrlKey) && event.key === "Enter") blurAllEditors();
|
|
203
|
+
});
|
|
204
|
+
shortcutsRegistered = true;
|
|
205
|
+
}
|
|
206
|
+
function blurAllEditors() {
|
|
207
|
+
activeEditors.forEach((instance) => {
|
|
208
|
+
instance.editor.commands.blur();
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
function getEditableHash(element) {
|
|
212
|
+
return element.dataset.upstartHash ?? null;
|
|
213
|
+
}
|
|
214
|
+
function cacheStyles(element) {
|
|
215
|
+
if (styleCache.has(element)) return;
|
|
216
|
+
styleCache.set(element, {
|
|
217
|
+
outline: element.style.outline || "",
|
|
218
|
+
outlineOffset: element.style.outlineOffset || "",
|
|
219
|
+
cursor: element.style.cursor || "",
|
|
220
|
+
transition: element.style.transition || ""
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
function applyHoverStyles(element) {
|
|
224
|
+
cacheStyles(element);
|
|
225
|
+
element.style.outline = "1px dashed #3b82f6";
|
|
226
|
+
element.style.outlineOffset = "2px";
|
|
227
|
+
element.style.cursor = "text";
|
|
228
|
+
}
|
|
229
|
+
function applyActiveStyles(element) {
|
|
230
|
+
cacheStyles(element);
|
|
231
|
+
element.style.outline = "2px solid #3b82f6";
|
|
232
|
+
element.style.outlineOffset = "2px";
|
|
233
|
+
element.style.cursor = "text";
|
|
234
|
+
}
|
|
235
|
+
function restoreStyles(element) {
|
|
236
|
+
const cached = styleCache.get(element);
|
|
237
|
+
if (!cached) {
|
|
238
|
+
element.style.outline = "";
|
|
239
|
+
element.style.outlineOffset = "";
|
|
240
|
+
element.style.cursor = "";
|
|
241
|
+
element.style.transition = "";
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
element.style.outline = cached.outline;
|
|
245
|
+
element.style.outlineOffset = cached.outlineOffset;
|
|
246
|
+
element.style.cursor = cached.cursor;
|
|
247
|
+
element.style.transition = cached.transition;
|
|
248
|
+
}
|
|
249
|
+
function createBubbleMenuElement() {
|
|
250
|
+
const menu = document.createElement("div");
|
|
251
|
+
menu.className = "upstart-editor-bubble-menu";
|
|
252
|
+
menu.style.cssText = "display: flex; gap: 6px; padding: 6px 8px; background: #111827; color: #ffffff; border-radius: 6px; font-size: 12px; box-shadow: 0 6px 16px rgba(0,0,0,0.2);";
|
|
253
|
+
for (const { label, command } of [
|
|
254
|
+
{
|
|
255
|
+
label: "B",
|
|
256
|
+
command: "bold"
|
|
257
|
+
},
|
|
258
|
+
{
|
|
259
|
+
label: "I",
|
|
260
|
+
command: "italic"
|
|
261
|
+
},
|
|
262
|
+
{
|
|
263
|
+
label: "S",
|
|
264
|
+
command: "strike"
|
|
265
|
+
}
|
|
266
|
+
]) {
|
|
267
|
+
const button = document.createElement("button");
|
|
268
|
+
button.type = "button";
|
|
269
|
+
button.textContent = label;
|
|
270
|
+
button.dataset.command = command;
|
|
271
|
+
button.style.cssText = "background: transparent; border: none; color: inherit; cursor: pointer; font-weight: 600;";
|
|
272
|
+
menu.appendChild(button);
|
|
273
|
+
}
|
|
274
|
+
return menu;
|
|
275
|
+
}
|
|
276
|
+
function wireBubbleMenu(menu, editor) {
|
|
277
|
+
menu.addEventListener("mousedown", (event) => {
|
|
278
|
+
event.preventDefault();
|
|
279
|
+
});
|
|
280
|
+
menu.addEventListener("click", (event) => {
|
|
281
|
+
const command = event.target?.dataset.command;
|
|
282
|
+
if (!command) return;
|
|
283
|
+
const chain = editor.chain().focus();
|
|
284
|
+
if (command === "bold") chain.toggleBold().run();
|
|
285
|
+
if (command === "italic") chain.toggleItalic().run();
|
|
286
|
+
if (command === "strike") chain.toggleStrike().run();
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
//#endregion
|
|
291
|
+
export { destroyAllActiveEditors, initTextEditor };
|
|
292
|
+
//# sourceMappingURL=text-editor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"text-editor.js","names":["DEFAULT_OPTIONS: Required<UpstartEditorOptions>","extensions: Extension[]"],"sources":["../../../src/vite-plugin-upstart-editor/runtime/text-editor.ts"],"sourcesContent":["import { Editor } from \"@tiptap/core\";\nimport type { Extension } from \"@tiptap/core\";\nimport { BubbleMenu } from \"@tiptap/extension-bubble-menu\";\nimport Placeholder from \"@tiptap/extension-placeholder\";\nimport StarterKit from \"@tiptap/starter-kit\";\nimport { getCurrentMode } from \"./index.js\";\nimport { sendToParent } from \"./utils.js\";\nimport type { EditorInstance, UpstartEditorOptions } from \"./types.js\";\n\nconst DEFAULT_OPTIONS: Required<UpstartEditorOptions> = {\n richTextElements: [\"p\", \"div\", \"article\", \"section\"],\n plainTextElements: [\"button\", \"a\", \"span\", \"h1\", \"h2\", \"h3\", \"h4\", \"h5\", \"h6\", \"label\"],\n bubbleMenu: true,\n placeholder: \"Start typing...\",\n autoSaveDelay: 1000,\n};\n\nconst activeEditors = new Map<string, EditorInstance>();\nconst saveTimeouts = new Map<string, number>();\nconst styleCache = new WeakMap<\n HTMLElement,\n { outline: string; outlineOffset: string; cursor: string; transition: string }\n>();\nlet isInitialized = false;\nlet shortcutsRegistered = false;\n\n/**\n * Initialize TipTap text editing for elements marked as editable.\n */\nexport function initTextEditor(options: UpstartEditorOptions = {}): void {\n if (typeof window === \"undefined\") {\n return;\n }\n\n if (isInitialized) {\n return;\n }\n\n try {\n const resolvedOptions = { ...DEFAULT_OPTIONS, ...options };\n const editables = document.querySelectorAll<HTMLElement>('[data-upstart-editable-text=\"true\"]');\n\n editables.forEach((element) => setupEditableElement(element, resolvedOptions));\n\n registerKeyboardShortcuts();\n isInitialized = true;\n\n console.log(\"[Upstart Editor] Text editor initialized\");\n } catch (error) {\n console.error(\"[Upstart Editor] Failed to initialize text editor:\", error);\n sendToParent({\n type: \"editor-error\",\n error: error instanceof Error ? error.message : \"Unknown error\",\n });\n }\n}\n\n/**\n * Destroy all active editors.\n */\nexport function destroyAllActiveEditors(): void {\n for (const hash of activeEditors.keys()) {\n destroyEditor(hash);\n }\n}\n\nfunction setupEditableElement(element: HTMLElement, options: Required<UpstartEditorOptions>): void {\n const hash = getEditableHash(element);\n if (!hash) {\n return;\n }\n\n cacheStyles(element);\n element.style.transition = \"outline 0.15s ease\";\n\n element.addEventListener(\"dblclick\", (event) => {\n if (getCurrentMode() !== \"edit\") {\n return;\n }\n\n event.preventDefault();\n event.stopPropagation();\n\n if (activeEditors.has(hash)) {\n return;\n }\n\n activateEditor(element, hash, options);\n });\n\n element.addEventListener(\"mouseenter\", () => {\n if (getCurrentMode() !== \"edit\") {\n return;\n }\n\n if (!activeEditors.has(hash)) {\n applyHoverStyles(element);\n }\n });\n\n element.addEventListener(\"mouseleave\", () => {\n if (!activeEditors.has(hash)) {\n restoreStyles(element);\n }\n });\n}\n\nfunction activateEditor(element: HTMLElement, hash: string, options: Required<UpstartEditorOptions>): void {\n const isRichText = shouldUseRichText(element, options);\n const editor = isRichText\n ? createRichTextEditor(element, hash, options)\n : createPlainTextEditor(element, hash, options);\n\n applyActiveStyles(element);\n activeEditors.set(hash, { editor, element, hash });\n}\n\nfunction createPlainTextEditor(\n element: HTMLElement,\n hash: string,\n options: Required<UpstartEditorOptions>,\n): Editor {\n return new Editor({\n element,\n extensions: [\n StarterKit.configure({\n heading: false,\n bold: false,\n italic: false,\n strike: false,\n blockquote: false,\n bulletList: false,\n orderedList: false,\n listItem: false,\n codeBlock: false,\n horizontalRule: false,\n }),\n Placeholder.configure({\n placeholder: \"Click to edit...\",\n }),\n ],\n content: element.textContent ?? \"\",\n editorProps: {\n attributes: {\n class: \"upstart-editor-active\",\n style: \"outline: 2px solid #3b82f6; outline-offset: 2px;\",\n },\n },\n onUpdate: ({ editor }) => {\n debouncedSave(hash, editor.getText(), options.autoSaveDelay);\n },\n onBlur: ({ editor }) => {\n saveText(hash, editor.getText());\n destroyEditor(hash);\n },\n onCreate: ({ editor }) => {\n editor.commands.focus(\"end\");\n },\n });\n}\n\nfunction createRichTextEditor(\n element: HTMLElement,\n hash: string,\n options: Required<UpstartEditorOptions>,\n): Editor {\n const bubbleMenuElement = createBubbleMenuElement();\n const extensions: Extension[] = [\n StarterKit,\n Placeholder.configure({\n placeholder: options.placeholder,\n }),\n ];\n\n if (options.bubbleMenu) {\n extensions.push(\n BubbleMenu.configure({\n element: bubbleMenuElement,\n }),\n );\n }\n\n const editor = new Editor({\n element,\n extensions,\n content: element.innerHTML,\n editorProps: {\n attributes: {\n class: \"upstart-editor-active\",\n style: \"outline: 2px solid #3b82f6; outline-offset: 2px;\",\n },\n },\n onUpdate: ({ editor: updatedEditor }) => {\n debouncedSave(hash, updatedEditor.getHTML(), options.autoSaveDelay);\n },\n onBlur: ({ editor: updatedEditor }) => {\n saveText(hash, updatedEditor.getHTML());\n destroyEditor(hash);\n },\n onCreate: ({ editor: createdEditor }) => {\n if (options.bubbleMenu) {\n wireBubbleMenu(bubbleMenuElement, createdEditor);\n }\n createdEditor.commands.focus(\"end\");\n },\n });\n\n return editor;\n}\n\nfunction shouldUseRichText(element: HTMLElement, options: Required<UpstartEditorOptions>): boolean {\n const tagName = element.tagName.toLowerCase();\n\n if (options.plainTextElements.includes(tagName)) {\n return false;\n }\n\n if (options.richTextElements.includes(tagName)) {\n return true;\n }\n\n return true;\n}\n\nfunction debouncedSave(hash: string, content: string, delay: number): void {\n const existingTimeout = saveTimeouts.get(hash);\n if (existingTimeout) {\n clearTimeout(existingTimeout);\n }\n\n const timeoutId = window.setTimeout(() => {\n sendToParent({\n type: \"text-update\",\n hash,\n newText: content,\n });\n }, delay);\n\n saveTimeouts.set(hash, timeoutId);\n}\n\nfunction saveText(hash: string, newText: string): void {\n try {\n sendToParent({\n type: \"text-save\",\n hash,\n newText,\n });\n\n console.log(\"[Upstart Editor] Text save message sent:\", hash);\n } catch (error) {\n console.error(\"[Upstart Editor] Failed to send save message:\", error);\n sendToParent({\n type: \"editor-error\",\n error: error instanceof Error ? error.message : \"Unknown error\",\n });\n }\n}\n\nfunction destroyEditor(hash: string): void {\n const instance = activeEditors.get(hash);\n if (!instance) {\n return;\n }\n\n instance.editor.destroy();\n restoreStyles(instance.element);\n activeEditors.delete(hash);\n\n const existingTimeout = saveTimeouts.get(hash);\n if (existingTimeout) {\n clearTimeout(existingTimeout);\n saveTimeouts.delete(hash);\n }\n}\n\nfunction registerKeyboardShortcuts(): void {\n if (shortcutsRegistered) {\n return;\n }\n\n document.addEventListener(\"keydown\", (event) => {\n if (event.key === \"Escape\") {\n blurAllEditors();\n }\n\n if ((event.metaKey || event.ctrlKey) && event.key === \"Enter\") {\n blurAllEditors();\n }\n });\n\n shortcutsRegistered = true;\n}\n\nfunction blurAllEditors(): void {\n activeEditors.forEach((instance) => {\n instance.editor.commands.blur();\n });\n}\n\nfunction getEditableHash(element: HTMLElement): string | null {\n return element.dataset.upstartHash ?? null;\n}\n\nfunction cacheStyles(element: HTMLElement): void {\n if (styleCache.has(element)) {\n return;\n }\n\n styleCache.set(element, {\n outline: element.style.outline || \"\",\n outlineOffset: element.style.outlineOffset || \"\",\n cursor: element.style.cursor || \"\",\n transition: element.style.transition || \"\",\n });\n}\n\nfunction applyHoverStyles(element: HTMLElement): void {\n cacheStyles(element);\n element.style.outline = \"1px dashed #3b82f6\";\n element.style.outlineOffset = \"2px\";\n element.style.cursor = \"text\";\n}\n\nfunction applyActiveStyles(element: HTMLElement): void {\n cacheStyles(element);\n element.style.outline = \"2px solid #3b82f6\";\n element.style.outlineOffset = \"2px\";\n element.style.cursor = \"text\";\n}\n\nfunction restoreStyles(element: HTMLElement): void {\n const cached = styleCache.get(element);\n if (!cached) {\n element.style.outline = \"\";\n element.style.outlineOffset = \"\";\n element.style.cursor = \"\";\n element.style.transition = \"\";\n return;\n }\n\n element.style.outline = cached.outline;\n element.style.outlineOffset = cached.outlineOffset;\n element.style.cursor = cached.cursor;\n element.style.transition = cached.transition;\n}\n\nfunction createBubbleMenuElement(): HTMLDivElement {\n const menu = document.createElement(\"div\");\n menu.className = \"upstart-editor-bubble-menu\";\n menu.style.cssText =\n \"display: flex; gap: 6px; padding: 6px 8px; background: #111827; color: #ffffff; \" +\n \"border-radius: 6px; font-size: 12px; box-shadow: 0 6px 16px rgba(0,0,0,0.2);\";\n\n const buttons = [\n { label: \"B\", command: \"bold\" },\n { label: \"I\", command: \"italic\" },\n { label: \"S\", command: \"strike\" },\n ];\n\n for (const { label, command } of buttons) {\n const button = document.createElement(\"button\");\n button.type = \"button\";\n button.textContent = label;\n button.dataset.command = command;\n button.style.cssText =\n \"background: transparent; border: none; color: inherit; cursor: pointer; font-weight: 600;\";\n menu.appendChild(button);\n }\n\n return menu;\n}\n\nfunction wireBubbleMenu(menu: HTMLDivElement, editor: Editor): void {\n menu.addEventListener(\"mousedown\", (event) => {\n event.preventDefault();\n });\n\n menu.addEventListener(\"click\", (event) => {\n const target = event.target as HTMLElement | null;\n const command = target?.dataset.command;\n\n if (!command) {\n return;\n }\n\n const chain = editor.chain().focus();\n\n if (command === \"bold\") {\n chain.toggleBold().run();\n }\n\n if (command === \"italic\") {\n chain.toggleItalic().run();\n }\n\n if (command === \"strike\") {\n chain.toggleStrike().run();\n }\n });\n}\n"],"mappings":";;;;;;;;AASA,MAAMA,kBAAkD;CACtD,kBAAkB;EAAC;EAAK;EAAO;EAAW;EAAU;CACpD,mBAAmB;EAAC;EAAU;EAAK;EAAQ;EAAM;EAAM;EAAM;EAAM;EAAM;EAAM;EAAQ;CACvF,YAAY;CACZ,aAAa;CACb,eAAe;CAChB;AAED,MAAM,gCAAgB,IAAI,KAA6B;AACvD,MAAM,+BAAe,IAAI,KAAqB;AAC9C,MAAM,6BAAa,IAAI,SAGpB;AACH,IAAI,gBAAgB;AACpB,IAAI,sBAAsB;;;;AAK1B,SAAgB,eAAe,UAAgC,EAAE,EAAQ;AACvE,KAAI,OAAO,WAAW,YACpB;AAGF,KAAI,cACF;AAGF,KAAI;EACF,MAAM,kBAAkB;GAAE,GAAG;GAAiB,GAAG;GAAS;AAG1D,EAFkB,SAAS,iBAA8B,wCAAsC,CAErF,SAAS,YAAY,qBAAqB,SAAS,gBAAgB,CAAC;AAE9E,6BAA2B;AAC3B,kBAAgB;AAEhB,UAAQ,IAAI,2CAA2C;UAChD,OAAO;AACd,UAAQ,MAAM,sDAAsD,MAAM;AAC1E,eAAa;GACX,MAAM;GACN,OAAO,iBAAiB,QAAQ,MAAM,UAAU;GACjD,CAAC;;;;;;AAON,SAAgB,0BAAgC;AAC9C,MAAK,MAAM,QAAQ,cAAc,MAAM,CACrC,eAAc,KAAK;;AAIvB,SAAS,qBAAqB,SAAsB,SAA+C;CACjG,MAAM,OAAO,gBAAgB,QAAQ;AACrC,KAAI,CAAC,KACH;AAGF,aAAY,QAAQ;AACpB,SAAQ,MAAM,aAAa;AAE3B,SAAQ,iBAAiB,aAAa,UAAU;AAC9C,MAAI,gBAAgB,KAAK,OACvB;AAGF,QAAM,gBAAgB;AACtB,QAAM,iBAAiB;AAEvB,MAAI,cAAc,IAAI,KAAK,CACzB;AAGF,iBAAe,SAAS,MAAM,QAAQ;GACtC;AAEF,SAAQ,iBAAiB,oBAAoB;AAC3C,MAAI,gBAAgB,KAAK,OACvB;AAGF,MAAI,CAAC,cAAc,IAAI,KAAK,CAC1B,kBAAiB,QAAQ;GAE3B;AAEF,SAAQ,iBAAiB,oBAAoB;AAC3C,MAAI,CAAC,cAAc,IAAI,KAAK,CAC1B,eAAc,QAAQ;GAExB;;AAGJ,SAAS,eAAe,SAAsB,MAAc,SAA+C;CAEzG,MAAM,SADa,kBAAkB,SAAS,QAAQ,GAElD,qBAAqB,SAAS,MAAM,QAAQ,GAC5C,sBAAsB,SAAS,MAAM,QAAQ;AAEjD,mBAAkB,QAAQ;AAC1B,eAAc,IAAI,MAAM;EAAE;EAAQ;EAAS;EAAM,CAAC;;AAGpD,SAAS,sBACP,SACA,MACA,SACQ;AACR,QAAO,IAAI,OAAO;EAChB;EACA,YAAY,CACV,WAAW,UAAU;GACnB,SAAS;GACT,MAAM;GACN,QAAQ;GACR,QAAQ;GACR,YAAY;GACZ,YAAY;GACZ,aAAa;GACb,UAAU;GACV,WAAW;GACX,gBAAgB;GACjB,CAAC,EACF,YAAY,UAAU,EACpB,aAAa,oBACd,CAAC,CACH;EACD,SAAS,QAAQ,eAAe;EAChC,aAAa,EACX,YAAY;GACV,OAAO;GACP,OAAO;GACR,EACF;EACD,WAAW,EAAE,aAAa;AACxB,iBAAc,MAAM,OAAO,SAAS,EAAE,QAAQ,cAAc;;EAE9D,SAAS,EAAE,aAAa;AACtB,YAAS,MAAM,OAAO,SAAS,CAAC;AAChC,iBAAc,KAAK;;EAErB,WAAW,EAAE,aAAa;AACxB,UAAO,SAAS,MAAM,MAAM;;EAE/B,CAAC;;AAGJ,SAAS,qBACP,SACA,MACA,SACQ;CACR,MAAM,oBAAoB,yBAAyB;CACnD,MAAMC,aAA0B,CAC9B,YACA,YAAY,UAAU,EACpB,aAAa,QAAQ,aACtB,CAAC,CACH;AAED,KAAI,QAAQ,WACV,YAAW,KACT,WAAW,UAAU,EACnB,SAAS,mBACV,CAAC,CACH;AA4BH,QAzBe,IAAI,OAAO;EACxB;EACA;EACA,SAAS,QAAQ;EACjB,aAAa,EACX,YAAY;GACV,OAAO;GACP,OAAO;GACR,EACF;EACD,WAAW,EAAE,QAAQ,oBAAoB;AACvC,iBAAc,MAAM,cAAc,SAAS,EAAE,QAAQ,cAAc;;EAErE,SAAS,EAAE,QAAQ,oBAAoB;AACrC,YAAS,MAAM,cAAc,SAAS,CAAC;AACvC,iBAAc,KAAK;;EAErB,WAAW,EAAE,QAAQ,oBAAoB;AACvC,OAAI,QAAQ,WACV,gBAAe,mBAAmB,cAAc;AAElD,iBAAc,SAAS,MAAM,MAAM;;EAEtC,CAAC;;AAKJ,SAAS,kBAAkB,SAAsB,SAAkD;CACjG,MAAM,UAAU,QAAQ,QAAQ,aAAa;AAE7C,KAAI,QAAQ,kBAAkB,SAAS,QAAQ,CAC7C,QAAO;AAGT,KAAI,QAAQ,iBAAiB,SAAS,QAAQ,CAC5C,QAAO;AAGT,QAAO;;AAGT,SAAS,cAAc,MAAc,SAAiB,OAAqB;CACzE,MAAM,kBAAkB,aAAa,IAAI,KAAK;AAC9C,KAAI,gBACF,cAAa,gBAAgB;CAG/B,MAAM,YAAY,OAAO,iBAAiB;AACxC,eAAa;GACX,MAAM;GACN;GACA,SAAS;GACV,CAAC;IACD,MAAM;AAET,cAAa,IAAI,MAAM,UAAU;;AAGnC,SAAS,SAAS,MAAc,SAAuB;AACrD,KAAI;AACF,eAAa;GACX,MAAM;GACN;GACA;GACD,CAAC;AAEF,UAAQ,IAAI,4CAA4C,KAAK;UACtD,OAAO;AACd,UAAQ,MAAM,iDAAiD,MAAM;AACrE,eAAa;GACX,MAAM;GACN,OAAO,iBAAiB,QAAQ,MAAM,UAAU;GACjD,CAAC;;;AAIN,SAAS,cAAc,MAAoB;CACzC,MAAM,WAAW,cAAc,IAAI,KAAK;AACxC,KAAI,CAAC,SACH;AAGF,UAAS,OAAO,SAAS;AACzB,eAAc,SAAS,QAAQ;AAC/B,eAAc,OAAO,KAAK;CAE1B,MAAM,kBAAkB,aAAa,IAAI,KAAK;AAC9C,KAAI,iBAAiB;AACnB,eAAa,gBAAgB;AAC7B,eAAa,OAAO,KAAK;;;AAI7B,SAAS,4BAAkC;AACzC,KAAI,oBACF;AAGF,UAAS,iBAAiB,YAAY,UAAU;AAC9C,MAAI,MAAM,QAAQ,SAChB,iBAAgB;AAGlB,OAAK,MAAM,WAAW,MAAM,YAAY,MAAM,QAAQ,QACpD,iBAAgB;GAElB;AAEF,uBAAsB;;AAGxB,SAAS,iBAAuB;AAC9B,eAAc,SAAS,aAAa;AAClC,WAAS,OAAO,SAAS,MAAM;GAC/B;;AAGJ,SAAS,gBAAgB,SAAqC;AAC5D,QAAO,QAAQ,QAAQ,eAAe;;AAGxC,SAAS,YAAY,SAA4B;AAC/C,KAAI,WAAW,IAAI,QAAQ,CACzB;AAGF,YAAW,IAAI,SAAS;EACtB,SAAS,QAAQ,MAAM,WAAW;EAClC,eAAe,QAAQ,MAAM,iBAAiB;EAC9C,QAAQ,QAAQ,MAAM,UAAU;EAChC,YAAY,QAAQ,MAAM,cAAc;EACzC,CAAC;;AAGJ,SAAS,iBAAiB,SAA4B;AACpD,aAAY,QAAQ;AACpB,SAAQ,MAAM,UAAU;AACxB,SAAQ,MAAM,gBAAgB;AAC9B,SAAQ,MAAM,SAAS;;AAGzB,SAAS,kBAAkB,SAA4B;AACrD,aAAY,QAAQ;AACpB,SAAQ,MAAM,UAAU;AACxB,SAAQ,MAAM,gBAAgB;AAC9B,SAAQ,MAAM,SAAS;;AAGzB,SAAS,cAAc,SAA4B;CACjD,MAAM,SAAS,WAAW,IAAI,QAAQ;AACtC,KAAI,CAAC,QAAQ;AACX,UAAQ,MAAM,UAAU;AACxB,UAAQ,MAAM,gBAAgB;AAC9B,UAAQ,MAAM,SAAS;AACvB,UAAQ,MAAM,aAAa;AAC3B;;AAGF,SAAQ,MAAM,UAAU,OAAO;AAC/B,SAAQ,MAAM,gBAAgB,OAAO;AACrC,SAAQ,MAAM,SAAS,OAAO;AAC9B,SAAQ,MAAM,aAAa,OAAO;;AAGpC,SAAS,0BAA0C;CACjD,MAAM,OAAO,SAAS,cAAc,MAAM;AAC1C,MAAK,YAAY;AACjB,MAAK,MAAM,UACT;AASF,MAAK,MAAM,EAAE,OAAO,aANJ;EACd;GAAE,OAAO;GAAK,SAAS;GAAQ;EAC/B;GAAE,OAAO;GAAK,SAAS;GAAU;EACjC;GAAE,OAAO;GAAK,SAAS;GAAU;EAClC,EAEyC;EACxC,MAAM,SAAS,SAAS,cAAc,SAAS;AAC/C,SAAO,OAAO;AACd,SAAO,cAAc;AACrB,SAAO,QAAQ,UAAU;AACzB,SAAO,MAAM,UACX;AACF,OAAK,YAAY,OAAO;;AAG1B,QAAO;;AAGT,SAAS,eAAe,MAAsB,QAAsB;AAClE,MAAK,iBAAiB,cAAc,UAAU;AAC5C,QAAM,gBAAgB;GACtB;AAEF,MAAK,iBAAiB,UAAU,UAAU;EAExC,MAAM,UADS,MAAM,QACG,QAAQ;AAEhC,MAAI,CAAC,QACH;EAGF,MAAM,QAAQ,OAAO,OAAO,CAAC,OAAO;AAEpC,MAAI,YAAY,OACd,OAAM,YAAY,CAAC,KAAK;AAG1B,MAAI,YAAY,SACd,OAAM,cAAc,CAAC,KAAK;AAG5B,MAAI,YAAY,SACd,OAAM,cAAc,CAAC,KAAK;GAE5B"}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { Editor } from "@tiptap/core";
|
|
2
|
+
|
|
3
|
+
//#region src/vite-plugin-upstart-editor/runtime/types.d.ts
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Editor mode
|
|
7
|
+
*/
|
|
8
|
+
type EditorMode = "preview" | "edit";
|
|
9
|
+
/**
|
|
10
|
+
* Element bounds used for UI positioning.
|
|
11
|
+
*/
|
|
12
|
+
interface ElementBounds {
|
|
13
|
+
top: number;
|
|
14
|
+
left: number;
|
|
15
|
+
width: number;
|
|
16
|
+
height: number;
|
|
17
|
+
right: number;
|
|
18
|
+
bottom: number;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Represents an active editor instance.
|
|
22
|
+
*/
|
|
23
|
+
interface EditorInstance {
|
|
24
|
+
editor: Editor;
|
|
25
|
+
element: HTMLElement;
|
|
26
|
+
hash: string;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Messages sent from iframe to parent editor.
|
|
30
|
+
*/
|
|
31
|
+
type EditorMessage = {
|
|
32
|
+
type: "text-update";
|
|
33
|
+
hash: string;
|
|
34
|
+
newText: string;
|
|
35
|
+
} | {
|
|
36
|
+
type: "text-save";
|
|
37
|
+
hash: string;
|
|
38
|
+
newText: string;
|
|
39
|
+
} | {
|
|
40
|
+
type: "editor-ready";
|
|
41
|
+
} | {
|
|
42
|
+
type: "editor-error";
|
|
43
|
+
error: string;
|
|
44
|
+
} | {
|
|
45
|
+
type: "element-hovered";
|
|
46
|
+
hash: string;
|
|
47
|
+
bounds: ElementBounds;
|
|
48
|
+
} | {
|
|
49
|
+
type: "element-clicked";
|
|
50
|
+
hash: string;
|
|
51
|
+
componentName: string;
|
|
52
|
+
filePath: string;
|
|
53
|
+
currentClassName: string;
|
|
54
|
+
bounds: ElementBounds;
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* Messages sent from parent to iframe.
|
|
58
|
+
*/
|
|
59
|
+
type ParentMessage = {
|
|
60
|
+
type: "set-mode";
|
|
61
|
+
mode: EditorMode;
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* Message wrapper sent via postMessage from iframe.
|
|
65
|
+
*/
|
|
66
|
+
interface UpstartEditorMessage {
|
|
67
|
+
source: "upstart-editor";
|
|
68
|
+
type: EditorMessage["type"];
|
|
69
|
+
[key: string]: any;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Message wrapper sent via postMessage from parent.
|
|
73
|
+
*/
|
|
74
|
+
interface UpstartParentMessage {
|
|
75
|
+
source: "upstart-editor-parent";
|
|
76
|
+
type: ParentMessage["type"];
|
|
77
|
+
[key: string]: any;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Configuration options for the Upstart Editor runtime.
|
|
81
|
+
*/
|
|
82
|
+
interface UpstartEditorOptions {
|
|
83
|
+
/**
|
|
84
|
+
* Elements that should use rich text editing (with formatting).
|
|
85
|
+
* @default ['p', 'div', 'article', 'section']
|
|
86
|
+
*/
|
|
87
|
+
richTextElements?: string[];
|
|
88
|
+
/**
|
|
89
|
+
* Elements that should use plain text editing (no formatting).
|
|
90
|
+
* @default ['button', 'a', 'span', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'label']
|
|
91
|
+
*/
|
|
92
|
+
plainTextElements?: string[];
|
|
93
|
+
/**
|
|
94
|
+
* Enable bubble menu for text selection.
|
|
95
|
+
* @default true
|
|
96
|
+
*/
|
|
97
|
+
bubbleMenu?: boolean;
|
|
98
|
+
/**
|
|
99
|
+
* Placeholder text for empty editors.
|
|
100
|
+
* @default 'Start typing...'
|
|
101
|
+
*/
|
|
102
|
+
placeholder?: string;
|
|
103
|
+
/**
|
|
104
|
+
* Auto-save delay in milliseconds.
|
|
105
|
+
* @default 1000
|
|
106
|
+
*/
|
|
107
|
+
autoSaveDelay?: number;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Build-time plugin options.
|
|
111
|
+
*/
|
|
112
|
+
interface UpstartEditorPluginOptions {
|
|
113
|
+
/**
|
|
114
|
+
* Enable or disable the editor.
|
|
115
|
+
* @default false
|
|
116
|
+
*/
|
|
117
|
+
enabled?: boolean;
|
|
118
|
+
/**
|
|
119
|
+
* Automatically inject editor initialization code.
|
|
120
|
+
* @default true
|
|
121
|
+
*/
|
|
122
|
+
autoInject?: boolean;
|
|
123
|
+
}
|
|
124
|
+
//#endregion
|
|
125
|
+
export { EditorInstance, EditorMessage, EditorMode, ElementBounds, ParentMessage, UpstartEditorMessage, UpstartEditorOptions, UpstartEditorPluginOptions, UpstartParentMessage };
|
|
126
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","names":[],"sources":["../../../src/vite-plugin-upstart-editor/runtime/types.ts"],"sourcesContent":[],"mappings":";;;;;;AAKA;AAKiB,KALL,UAAA,GAKkB,SAAA,GAAA,MAAA;AAY9B;AASA;AAkBA;AAKiB,UA5CA,aAAA,CA4CoB;EASpB,GAAA,EAAA,MAAA;EASA,IAAA,EAAA,MAAA;EAmCA,KAAA,EAAA,MAAA;;;;;;;;UArFA,cAAA;UACP;WACC;;;;;;KAOC,aAAA;;;;;;;;;;;;;;;;UAKyC;;;;;;;UAOvC;;;;;KAMF,aAAA;;QAA0C;;;;;UAKrC,oBAAA;;QAET;;;;;;UAOS,oBAAA;;QAET;;;;;;UAOS,oBAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UAmCA,0BAAA"}
|