fixdog 0.0.1

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.
@@ -0,0 +1,171 @@
1
+ /**
2
+ * Sidebar initializer - mounts the UiDog sidebar in a shadow DOM for style isolation
3
+ */
4
+
5
+ import { createRoot, Root } from "react-dom/client";
6
+ import { createElement } from "react";
7
+ import { UiDogSidebarReact } from "./components/UiDogSidebarReact";
8
+ import sidebarStyles from "./styles/sidebarStyles";
9
+ import type { ElementInfo, SidebarConfig } from "./types";
10
+
11
+ let sidebarRoot: Root | null = null;
12
+ let sidebarContainer: HTMLElement | null = null;
13
+ let shadowRoot: ShadowRoot | null = null;
14
+ let config: SidebarConfig = { apiEndpoint: "https://api.ui.dog" };
15
+
16
+ // Current sidebar state
17
+ let currentState: {
18
+ isOpen: boolean;
19
+ elementInfo: ElementInfo | null;
20
+ editorUrl: string;
21
+ } = {
22
+ isOpen: false,
23
+ elementInfo: null,
24
+ editorUrl: "",
25
+ };
26
+
27
+ /**
28
+ * Initialize the sidebar with configuration
29
+ */
30
+ export function initializeSidebar(sidebarConfig: SidebarConfig): void {
31
+ if (typeof window === "undefined") return;
32
+
33
+ config = sidebarConfig;
34
+
35
+ // Check if already initialized
36
+ if (document.getElementById("uidog-next-sidebar-root")) {
37
+ return;
38
+ }
39
+
40
+ // Create container
41
+ sidebarContainer = document.createElement("div");
42
+ sidebarContainer.id = "uidog-next-sidebar-root";
43
+ document.body.appendChild(sidebarContainer);
44
+
45
+ // Create shadow DOM for style isolation
46
+ shadowRoot = sidebarContainer.attachShadow({ mode: "open" });
47
+
48
+ // Inject styles
49
+ const styleElement = document.createElement("style");
50
+ styleElement.textContent = sidebarStyles;
51
+ shadowRoot.appendChild(styleElement);
52
+
53
+ // Create mount point
54
+ const mountPoint = document.createElement("div");
55
+ mountPoint.id = "uidog-sidebar-mount";
56
+ shadowRoot.appendChild(mountPoint);
57
+
58
+ // Create React root
59
+ sidebarRoot = createRoot(mountPoint);
60
+
61
+ // Initial render (empty)
62
+ render();
63
+ }
64
+
65
+ /**
66
+ * Render the sidebar based on current state
67
+ */
68
+ function render(): void {
69
+ if (!sidebarRoot) return;
70
+
71
+ const { isOpen, elementInfo, editorUrl } = currentState;
72
+
73
+ if (!isOpen || !elementInfo) {
74
+ sidebarRoot.render(null);
75
+ return;
76
+ }
77
+
78
+ sidebarRoot.render(
79
+ createElement(UiDogSidebarReact, {
80
+ elementInfo,
81
+ editorUrl,
82
+ onClose: closeSidebar,
83
+ apiEndpoint: config.apiEndpoint,
84
+ })
85
+ );
86
+ }
87
+
88
+ /**
89
+ * Open the sidebar with element information
90
+ */
91
+ export function openSidebar(elementInfo: ElementInfo, editorUrl: string): void {
92
+ currentState = {
93
+ isOpen: true,
94
+ elementInfo,
95
+ editorUrl,
96
+ };
97
+
98
+ // Update global state for debugging
99
+ if (typeof window !== "undefined") {
100
+ window.__UIDOG_SIDEBAR__ = {
101
+ isOpen: true,
102
+ elementInfo,
103
+ editorUrl,
104
+ };
105
+ }
106
+
107
+ render();
108
+ }
109
+
110
+ /**
111
+ * Close the sidebar
112
+ */
113
+ export function closeSidebar(): void {
114
+ currentState = {
115
+ isOpen: false,
116
+ elementInfo: null,
117
+ editorUrl: "",
118
+ };
119
+
120
+ // Update global state
121
+ if (typeof window !== "undefined") {
122
+ window.__UIDOG_SIDEBAR__ = {
123
+ isOpen: false,
124
+ elementInfo: null,
125
+ editorUrl: null,
126
+ };
127
+ }
128
+
129
+ render();
130
+ }
131
+
132
+ /**
133
+ * Check if the sidebar is currently open
134
+ */
135
+ export function isSidebarOpen(): boolean {
136
+ return currentState.isOpen;
137
+ }
138
+
139
+ /**
140
+ * Get current sidebar state
141
+ */
142
+ export function getSidebarState(): typeof currentState {
143
+ return { ...currentState };
144
+ }
145
+
146
+ /**
147
+ * Cleanup sidebar (for testing or unmounting)
148
+ */
149
+ export function cleanupSidebar(): void {
150
+ if (sidebarRoot) {
151
+ sidebarRoot.unmount();
152
+ sidebarRoot = null;
153
+ }
154
+
155
+ if (sidebarContainer && sidebarContainer.parentNode) {
156
+ sidebarContainer.parentNode.removeChild(sidebarContainer);
157
+ sidebarContainer = null;
158
+ }
159
+
160
+ shadowRoot = null;
161
+
162
+ currentState = {
163
+ isOpen: false,
164
+ elementInfo: null,
165
+ editorUrl: "",
166
+ };
167
+
168
+ if (typeof window !== "undefined") {
169
+ delete window.__UIDOG_SIDEBAR__;
170
+ }
171
+ }
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Source resolver - builds editor URLs from source location information
3
+ */
4
+
5
+ import type { SourceLocation, EditorType } from "./types";
6
+
7
+ const EDITOR_SCHEMES: Record<EditorType, string> = {
8
+ vscode: "vscode://file/{path}:{line}:{column}",
9
+ "vscode-insiders": "vscode-insiders://file/{path}:{line}:{column}",
10
+ cursor: "cursor://file/{path}:{line}:{column}",
11
+ webstorm: "webstorm://open?file={path}&line={line}&column={column}",
12
+ atom: "atom://core/open/file?filename={path}&line={line}&column={column}",
13
+ sublime: "subl://open?url=file://{path}&line={line}&column={column}",
14
+ };
15
+
16
+ /**
17
+ * Build an editor URL from source location information
18
+ */
19
+ export function buildEditorUrl(
20
+ source: SourceLocation,
21
+ editor: EditorType = "cursor",
22
+ projectPath: string = ""
23
+ ): string {
24
+ const template = EDITOR_SCHEMES[editor] || EDITOR_SCHEMES.cursor;
25
+
26
+ // Normalize and build full path
27
+ let fullPath = normalizeFileName(source.fileName);
28
+
29
+ // If path is relative and we have a project path, make it absolute
30
+ if (projectPath && !fullPath.startsWith("/")) {
31
+ const normalizedProjectPath = projectPath.endsWith("/")
32
+ ? projectPath.slice(0, -1)
33
+ : projectPath;
34
+ fullPath = `${normalizedProjectPath}/${fullPath}`;
35
+ }
36
+
37
+ return template
38
+ .replace("{path}", fullPath)
39
+ .replace("{line}", String(source.lineNumber || 1))
40
+ .replace("{column}", String(source.columnNumber || 1));
41
+ }
42
+
43
+ /**
44
+ * Normalize file name by removing bundler prefixes and query strings
45
+ */
46
+ export function normalizeFileName(fileName: string): string {
47
+ if (!fileName) return "";
48
+
49
+ let normalized = fileName;
50
+
51
+ // Remove common bundler prefixes
52
+ const prefixPatterns = [
53
+ /^webpack:\/\/[^/]*\//,
54
+ /^webpack-internal:\/\/\//,
55
+ /^file:\/\//,
56
+ /^about:react/,
57
+ /^\.\//,
58
+ /^https?:\/\/localhost:\d+\//,
59
+ /^https?:\/\/[^/]+\/@fs\//,
60
+ /^https?:\/\/[^/]+\//,
61
+ /^\/@fs\//,
62
+ /^@fs\//,
63
+ ];
64
+
65
+ for (const pattern of prefixPatterns) {
66
+ normalized = normalized.replace(pattern, "");
67
+ }
68
+
69
+ // Remove query strings and hash fragments
70
+ normalized = normalized.split("?")[0].split("#")[0];
71
+
72
+ // Remove leading slashes if duplicated
73
+ normalized = normalized.replace(/^\/+/, "/");
74
+
75
+ return normalized;
76
+ }
77
+
78
+ /**
79
+ * Check if a file name represents actual source code (not bundled/internal)
80
+ */
81
+ export function isSourceFile(fileName: string): boolean {
82
+ if (!fileName) return false;
83
+
84
+ const normalized = normalizeFileName(fileName);
85
+
86
+ // Exclude patterns
87
+ const excludePatterns = [
88
+ /node_modules/,
89
+ /react-dom/,
90
+ /^react\//,
91
+ /\.next\//,
92
+ /_next\//,
93
+ /webpack/,
94
+ /@vite\//,
95
+ /vite\/client/,
96
+ /\[eval\]/,
97
+ /<anonymous>/,
98
+ ];
99
+
100
+ for (const pattern of excludePatterns) {
101
+ if (pattern.test(normalized)) {
102
+ return false;
103
+ }
104
+ }
105
+
106
+ // Include patterns (common source file extensions)
107
+ const includeExtensions = [".tsx", ".ts", ".jsx", ".js", ".mjs", ".cjs"];
108
+
109
+ return includeExtensions.some((ext) =>
110
+ normalized.toLowerCase().endsWith(ext)
111
+ );
112
+ }
113
+
114
+ /**
115
+ * Get the file name without path for display
116
+ */
117
+ export function getShortFileName(filePath: string): string {
118
+ const normalized = normalizeFileName(filePath);
119
+ const parts = normalized.split("/");
120
+ return parts[parts.length - 1] || normalized;
121
+ }