)
- if (isJSXMemberExpression(jsxOpeningElement.name)) {
- let current = jsxOpeningElement.name;
- while (isJSXMemberExpression(current)) {
- current = current.property;
- }
- if (isJSXIdentifier(current)) {
- return COMPONENT_BLACKLIST.has(current.name);
- }
- }
-
- return false;
-}
-
-/**
- * Generates code from an AST node
- * @param {object} node - Babel AST node
- * @param {object} options - Generator options
- * @returns {string} Generated code
- */
-export function generateCode(node, options = {}) {
- const generateFunction = generate.default || generate;
- const output = generateFunction(node, options);
- return output.code;
-}
-
-/**
- * Generates a full source file from AST with source maps
- * @param {object} ast - Babel AST
- * @param {string} sourceFileName - Source file name for source map
- * @param {string} originalCode - Original source code
- * @returns {{code: string, map: object}} - Object containing generated code and source map
- */
-export function generateSourceWithMap(ast, sourceFileName, originalCode) {
- const generateFunction = generate.default || generate;
- return generateFunction(ast, {
- sourceMaps: true,
- sourceFileName,
- }, originalCode);
-}
-
-/**
- * Extracts code blocks from a JSX element at a specific location
- * @param {string} filePath - Relative file path
- * @param {number} line - Line number
- * @param {number} column - Column number
- * @param {object} [domContext] - Optional DOM context to return on failure
- * @returns {{success: boolean, filePath?: string, specificLine?: string, error?: string, domContext?: object}} - Object with metadata for LLM
- */
-export function extractCodeBlocks(filePath, line, column, domContext) {
- try {
- // Validate file path
- const validation = validateFilePath(filePath);
- if (!validation.isValid) {
- return { success: false, error: validation.error, domContext };
- }
-
- // Parse AST
- const ast = parseFileToAST(validation.absolutePath);
-
- // Find target node
- const targetNodePath = findJSXElementAtPosition(ast, line, column);
-
- if (!targetNodePath) {
- return { success: false, error: 'Target node not found at specified line/column', domContext };
- }
-
- // Check if the target node is a blacklisted component
- const isBlacklisted = isBlacklistedComponent(targetNodePath.node);
-
- if (isBlacklisted) {
- return {
- success: true,
- filePath,
- specificLine: '',
- };
- }
-
- // Get specific line code
- const specificLine = generateCode(targetNodePath.parentPath?.node || targetNodePath.node);
-
- return {
- success: true,
- filePath,
- specificLine,
- };
- } catch (error) {
- console.error('[ast-utils] Error extracting code blocks:', error);
- return { success: false, error: 'Failed to extract code blocks', domContext };
- }
-}
-
-/**
- * Project root path
- */
-export { VITE_PROJECT_ROOT };
diff --git a/plugins/visual-editor/edit-mode-script.js b/plugins/visual-editor/edit-mode-script.js
deleted file mode 100644
index e83045e..0000000
--- a/plugins/visual-editor/edit-mode-script.js
+++ /dev/null
@@ -1,356 +0,0 @@
-import { POPUP_STYLES } from "./plugins/visual-editor/visual-editor-config.js";
-
-const PLUGIN_APPLY_EDIT_API_URL = "/api/apply-edit";
-
-const ALLOWED_PARENT_ORIGINS = [
- "https://horizons.hostinger.com",
- "https://horizons.hostinger.dev",
- "https://horizons-frontend-local.hostinger.dev",
- "http://localhost:4000",
-];
-
-let disabledTooltipElement = null;
-let currentDisabledHoverElement = null;
-
-let translations = {
- disabledTooltipText: "This text can be changed only through chat.",
- disabledTooltipTextImage: "This image can only be changed through chat.",
-};
-
-let areStylesInjected = false;
-
-let globalEventHandlers = null;
-
-let currentEditingInfo = null;
-
-function injectPopupStyles() {
- if (areStylesInjected) return;
-
- const styleElement = document.createElement("style");
- styleElement.id = "inline-editor-styles";
- styleElement.textContent = POPUP_STYLES;
- document.head.appendChild(styleElement);
- areStylesInjected = true;
-}
-
-function findEditableElementAtPoint(event) {
- let editableElement = event.target.closest("[data-edit-id]");
-
- if (editableElement) {
- return editableElement;
- }
-
- const elementsAtPoint = document.elementsFromPoint(
- event.clientX,
- event.clientY
- );
-
- const found = elementsAtPoint.find(
- (el) => el !== event.target && el.hasAttribute("data-edit-id")
- );
- if (found) return found;
-
- return null;
-}
-
-function findDisabledElementAtPoint(event) {
- const direct = event.target.closest("[data-edit-disabled]");
- if (direct) return direct;
- const elementsAtPoint = document.elementsFromPoint(
- event.clientX,
- event.clientY
- );
- const found = elementsAtPoint.find(
- (el) => el !== event.target && el.hasAttribute("data-edit-disabled")
- );
- if (found) return found;
- return null;
-}
-
-function showPopup(targetElement, editId, currentContent, isImage = false) {
- currentEditingInfo = { editId, targetElement };
-
- const parentOrigin = getParentOrigin();
-
- if (parentOrigin && ALLOWED_PARENT_ORIGINS.includes(parentOrigin)) {
- const eventType = isImage ? "imageEditEnter" : "editEnter";
-
- window.parent.postMessage(
- {
- type: eventType,
- payload: { currentText: currentContent },
- },
- parentOrigin
- );
- }
-}
-
-function handleGlobalEvent(event) {
- if (
- !document.getElementById("root")?.getAttribute("data-edit-mode-enabled")
- ) {
- return;
- }
-
- // Don't handle if selection mode is active
- if (document.getElementById("root")?.getAttribute("data-selection-mode-enabled") === "true") {
- return;
- }
-
- if (event.target.closest("#inline-editor-popup")) {
- return;
- }
-
- const editableElement = findEditableElementAtPoint(event);
-
- if (editableElement) {
- event.preventDefault();
- event.stopPropagation();
- event.stopImmediatePropagation();
-
- if (event.type === "click") {
- const editId = editableElement.getAttribute("data-edit-id");
- if (!editId) {
- console.warn("[INLINE EDITOR] Clicked element missing data-edit-id");
- return;
- }
-
- const isImage = editableElement.tagName.toLowerCase() === "img";
- let currentContent = "";
-
- if (isImage) {
- currentContent = editableElement.getAttribute("src") || "";
- } else {
- currentContent = editableElement.textContent || "";
- }
-
- showPopup(editableElement, editId, currentContent, isImage);
- }
- } else {
- event.preventDefault();
- event.stopPropagation();
- event.stopImmediatePropagation();
- }
-}
-
-function getParentOrigin() {
- if (
- window.location.ancestorOrigins &&
- window.location.ancestorOrigins.length > 0
- ) {
- return window.location.ancestorOrigins[0];
- }
-
- if (document.referrer) {
- try {
- return new URL(document.referrer).origin;
- } catch (e) {
- console.warn("Invalid referrer URL:", document.referrer);
- }
- }
-
- return null;
-}
-
-async function handleEditSave(updatedText) {
- const newText = updatedText
- // Replacing characters that cause Babel parser to crash
- .replace(//g, ">")
- .replace(/{/g, "{")
- .replace(/}/g, "}");
-
- const { editId } = currentEditingInfo;
-
- try {
- const response = await fetch(PLUGIN_APPLY_EDIT_API_URL, {
- method: "POST",
- headers: {
- "Content-Type": "application/json",
- },
- body: JSON.stringify({
- editId: editId,
- newFullText: newText,
- }),
- });
-
- const result = await response.json();
- if (result.success) {
- const parentOrigin = getParentOrigin();
- if (parentOrigin && ALLOWED_PARENT_ORIGINS.includes(parentOrigin)) {
- window.parent.postMessage(
- {
- type: "editApplied",
- payload: {
- editId: editId,
- fileContent: result.newFileContent,
- beforeCode: result.beforeCode,
- afterCode: result.afterCode,
- },
- },
- parentOrigin
- );
- } else {
- console.error("Unauthorized parent origin:", parentOrigin);
- }
- } else {
- console.error(
- `[vite][visual-editor] Error saving changes: ${result.error}`
- );
- }
- } catch (error) {
- console.error(
- `[vite][visual-editor] Error during fetch for ${editId}:`,
- error
- );
- }
-}
-
-function createDisabledTooltip() {
- if (disabledTooltipElement) return;
-
- disabledTooltipElement = document.createElement("div");
- disabledTooltipElement.id = "inline-editor-disabled-tooltip";
- document.body.appendChild(disabledTooltipElement);
-}
-
-function showDisabledTooltip(targetElement, isImage = false) {
- if (!disabledTooltipElement) createDisabledTooltip();
-
- disabledTooltipElement.textContent = isImage
- ? translations.disabledTooltipTextImage
- : translations.disabledTooltipText;
-
- if (!disabledTooltipElement.isConnected) {
- document.body.appendChild(disabledTooltipElement);
- }
- disabledTooltipElement.classList.add("tooltip-active");
-
- const tooltipWidth = disabledTooltipElement.offsetWidth;
- const tooltipHeight = disabledTooltipElement.offsetHeight;
- const rect = targetElement.getBoundingClientRect();
-
- // Ensures that tooltip is not off the screen with 5px margin
- let newLeft = rect.left + window.scrollX + rect.width / 2 - tooltipWidth / 2;
- let newTop = rect.bottom + window.scrollY + 5;
-
- if (newLeft < window.scrollX) {
- newLeft = window.scrollX + 5;
- }
- if (newLeft + tooltipWidth > window.innerWidth + window.scrollX) {
- newLeft = window.innerWidth + window.scrollX - tooltipWidth - 5;
- }
- if (newTop + tooltipHeight > window.innerHeight + window.scrollY) {
- newTop = rect.top + window.scrollY - tooltipHeight - 5;
- }
- if (newTop < window.scrollY) {
- newTop = window.scrollY + 5;
- }
-
- disabledTooltipElement.style.left = `${newLeft}px`;
- disabledTooltipElement.style.top = `${newTop}px`;
-}
-
-function hideDisabledTooltip() {
- if (disabledTooltipElement) {
- disabledTooltipElement.classList.remove("tooltip-active");
- }
-}
-
-function handleDisabledElementHover(event) {
- const isImage = event.currentTarget.tagName.toLowerCase() === "img";
-
- showDisabledTooltip(event.currentTarget, isImage);
-}
-
-function handleDisabledElementLeave() {
- hideDisabledTooltip();
-}
-
-function handleDisabledGlobalHover(event) {
- const disabledElement = findDisabledElementAtPoint(event);
- if (disabledElement) {
- if (currentDisabledHoverElement !== disabledElement) {
- currentDisabledHoverElement = disabledElement;
- const isImage = disabledElement.tagName.toLowerCase() === "img";
- showDisabledTooltip(disabledElement, isImage);
- }
- } else {
- if (currentDisabledHoverElement) {
- currentDisabledHoverElement = null;
- hideDisabledTooltip();
- }
- }
-}
-
-function enableEditMode() {
- // Don't enable if selection mode is active
- if (document.getElementById("root")?.getAttribute("data-selection-mode-enabled") === "true") {
- console.warn("[EDIT MODE] Cannot enable edit mode while selection mode is active");
- return;
- }
-
- document
- .getElementById("root")
- ?.setAttribute("data-edit-mode-enabled", "true");
-
- injectPopupStyles();
-
- if (!globalEventHandlers) {
- globalEventHandlers = {
- mousedown: handleGlobalEvent,
- pointerdown: handleGlobalEvent,
- click: handleGlobalEvent,
- };
-
- Object.entries(globalEventHandlers).forEach(([eventType, handler]) => {
- document.addEventListener(eventType, handler, true);
- });
- }
-
- document.addEventListener("mousemove", handleDisabledGlobalHover, true);
-
- document.querySelectorAll("[data-edit-disabled]").forEach((el) => {
- el.removeEventListener("mouseenter", handleDisabledElementHover);
- el.addEventListener("mouseenter", handleDisabledElementHover);
- el.removeEventListener("mouseleave", handleDisabledElementLeave);
- el.addEventListener("mouseleave", handleDisabledElementLeave);
- });
-}
-
-function disableEditMode() {
- document.getElementById("root")?.removeAttribute("data-edit-mode-enabled");
-
- hideDisabledTooltip();
-
- if (globalEventHandlers) {
- Object.entries(globalEventHandlers).forEach(([eventType, handler]) => {
- document.removeEventListener(eventType, handler, true);
- });
- globalEventHandlers = null;
- }
-
- document.removeEventListener("mousemove", handleDisabledGlobalHover, true);
- currentDisabledHoverElement = null;
-
- document.querySelectorAll("[data-edit-disabled]").forEach((el) => {
- el.removeEventListener("mouseenter", handleDisabledElementHover);
- el.removeEventListener("mouseleave", handleDisabledElementLeave);
- });
-}
-
-window.addEventListener("message", function (event) {
- if (event.data?.type === "edit-save") {
- handleEditSave(event.data?.payload?.newText);
- }
- if (event.data?.type === "enable-edit-mode") {
- if (event.data?.translations) {
- translations = { ...translations, ...event.data.translations };
- }
-
- enableEditMode();
- }
- if (event.data?.type === "disable-edit-mode") {
- disableEditMode();
- }
-});
diff --git a/plugins/visual-editor/visual-editor-config.js b/plugins/visual-editor/visual-editor-config.js
deleted file mode 100644
index a5fa052..0000000
--- a/plugins/visual-editor/visual-editor-config.js
+++ /dev/null
@@ -1,137 +0,0 @@
-export const POPUP_STYLES = `
-#inline-editor-popup {
- width: 360px;
- position: fixed;
- z-index: 10000;
- background: #161718;
- color: white;
- border: 1px solid #4a5568;
- border-radius: 16px;
- padding: 8px;
- box-shadow: 0 4px 12px rgba(0,0,0,0.2);
- flex-direction: column;
- gap: 10px;
- display: none;
-}
-
-@media (max-width: 768px) {
- #inline-editor-popup {
- width: calc(100% - 20px);
- }
-}
-
-#inline-editor-popup.is-active {
- display: flex;
- top: 50%;
- left: 50%;
- transform: translate(-50%, -50%);
-}
-
-#inline-editor-popup.is-disabled-view {
- padding: 10px 15px;
-}
-
-#inline-editor-popup textarea {
- height: 100px;
- padding: 4px 8px;
- background: transparent;
- color: white;
- font-family: inherit;
- font-size: 0.875rem;
- line-height: 1.42;
- resize: none;
- outline: none;
-}
-
-#inline-editor-popup .button-container {
- display: flex;
- justify-content: flex-end;
- gap: 10px;
-}
-
-#inline-editor-popup .popup-button {
- border: none;
- padding: 6px 16px;
- border-radius: 8px;
- cursor: pointer;
- font-size: 0.75rem;
- font-weight: 700;
- height: 34px;
- outline: none;
-}
-
-#inline-editor-popup .save-button {
- background: #673de6;
- color: white;
-}
-
-#inline-editor-popup .cancel-button {
- background: transparent;
- border: 1px solid #3b3d4a;
- color: white;
-
- &:hover {
- background:#474958;
- }
-}
-`;
-
-export function getPopupHTMLTemplate(saveLabel, cancelLabel) {
- return `
-
-
-
-
-
- `;
-}
-
-export const EDIT_MODE_STYLES = `
- #root[data-edit-mode-enabled="true"] [data-edit-id] {
- cursor: pointer;
- outline: 2px dashed #357DF9;
- outline-offset: 2px;
- min-height: 1em;
- }
- #root[data-edit-mode-enabled="true"] img[data-edit-id] {
- outline-offset: -2px;
- }
- #root[data-edit-mode-enabled="true"] {
- cursor: pointer;
- }
- #root[data-edit-mode-enabled="true"] [data-edit-id]:hover {
- background-color: #357DF933;
- outline-color: #357DF9;
- }
-
- @keyframes fadeInTooltip {
- from {
- opacity: 0;
- transform: translateY(5px);
- }
- to {
- opacity: 1;
- transform: translateY(0);
- }
- }
-
- #inline-editor-disabled-tooltip {
- display: none;
- opacity: 0;
- position: absolute;
- background-color: #1D1E20;
- color: white;
- padding: 4px 8px;
- border-radius: 8px;
- z-index: 10001;
- font-size: 14px;
- border: 1px solid #3B3D4A;
- max-width: 184px;
- text-align: center;
- }
-
- #inline-editor-disabled-tooltip.tooltip-active {
- display: block;
- animation: fadeInTooltip 0.2s ease-out forwards;
- }
-`;
diff --git a/plugins/visual-editor/vite-plugin-edit-mode.js b/plugins/visual-editor/vite-plugin-edit-mode.js
deleted file mode 100644
index 58790b8..0000000
--- a/plugins/visual-editor/vite-plugin-edit-mode.js
+++ /dev/null
@@ -1,32 +0,0 @@
-import { readFileSync } from 'fs';
-import { resolve } from 'path';
-import { fileURLToPath } from 'url';
-import { EDIT_MODE_STYLES } from './visual-editor-config';
-
-const __filename = fileURLToPath(import.meta.url);
-const __dirname = resolve(__filename, '..');
-
-export default function inlineEditDevPlugin() {
- return {
- name: 'vite:inline-edit-dev',
- apply: 'serve',
- transformIndexHtml() {
- const scriptPath = resolve(__dirname, 'edit-mode-script.js');
- const scriptContent = readFileSync(scriptPath, 'utf-8');
-
- return [
- {
- tag: 'script',
- attrs: { type: 'module' },
- children: scriptContent,
- injectTo: 'body'
- },
- {
- tag: 'style',
- children: EDIT_MODE_STYLES,
- injectTo: 'head'
- }
- ];
- }
- };
-}
diff --git a/plugins/visual-editor/vite-plugin-react-inline-editor.js b/plugins/visual-editor/vite-plugin-react-inline-editor.js
deleted file mode 100644
index 315afea..0000000
--- a/plugins/visual-editor/vite-plugin-react-inline-editor.js
+++ /dev/null
@@ -1,365 +0,0 @@
-import path from 'path';
-import { parse } from '@babel/parser';
-import traverseBabel from '@babel/traverse';
-import * as t from '@babel/types';
-import fs from 'fs';
-import {
- validateFilePath,
- parseFileToAST,
- findJSXElementAtPosition,
- generateCode,
- generateSourceWithMap,
- VITE_PROJECT_ROOT
-} from '../utils/ast-utils.js';
-
-const EDITABLE_HTML_TAGS = ["a", "Button", "button", "p", "span", "h1", "h2", "h3", "h4", "h5", "h6", "label", "Label", "img"];
-
-function parseEditId(editId) {
- const parts = editId.split(':');
-
- if (parts.length < 3) {
- return null;
- }
-
- const column = parseInt(parts.at(-1), 10);
- const line = parseInt(parts.at(-2), 10);
- const filePath = parts.slice(0, -2).join(':');
-
- if (!filePath || isNaN(line) || isNaN(column)) {
- return null;
- }
-
- return { filePath, line, column };
-}
-
-function checkTagNameEditable(openingElementNode, editableTagsList) {
- if (!openingElementNode || !openingElementNode.name) return false;
- const nameNode = openingElementNode.name;
-
- // Check 1: Direct name (for ,