@gxp-dev/tools 2.0.5
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/.github/workflows/npm-publish.yml +48 -0
- package/CLAUDE.md +400 -0
- package/README.md +247 -0
- package/REFACTOR_PLAN.md +194 -0
- package/bin/gx-devtools.js +87 -0
- package/bin/lib/cli.js +251 -0
- package/bin/lib/commands/assets.js +337 -0
- package/bin/lib/commands/build.js +259 -0
- package/bin/lib/commands/datastore.js +433 -0
- package/bin/lib/commands/dev.js +328 -0
- package/bin/lib/commands/extensions.js +298 -0
- package/bin/lib/commands/index.js +35 -0
- package/bin/lib/commands/init.js +307 -0
- package/bin/lib/commands/publish.js +189 -0
- package/bin/lib/commands/socket.js +158 -0
- package/bin/lib/commands/ssl.js +47 -0
- package/bin/lib/constants.js +120 -0
- package/bin/lib/tui/App.tsx +600 -0
- package/bin/lib/tui/components/CommandInput.tsx +278 -0
- package/bin/lib/tui/components/GeminiPanel.tsx +161 -0
- package/bin/lib/tui/components/Header.tsx +27 -0
- package/bin/lib/tui/components/LogPanel.tsx +122 -0
- package/bin/lib/tui/components/TabBar.tsx +56 -0
- package/bin/lib/tui/components/WelcomeScreen.tsx +80 -0
- package/bin/lib/tui/index.tsx +63 -0
- package/bin/lib/tui/services/ExtensionService.ts +122 -0
- package/bin/lib/tui/services/GeminiService.ts +395 -0
- package/bin/lib/tui/services/ServiceManager.ts +336 -0
- package/bin/lib/tui/services/SocketService.ts +204 -0
- package/bin/lib/tui/services/ViteService.ts +107 -0
- package/bin/lib/tui/services/index.ts +13 -0
- package/bin/lib/utils/files.js +180 -0
- package/bin/lib/utils/index.js +17 -0
- package/bin/lib/utils/paths.js +138 -0
- package/bin/lib/utils/prompts.js +71 -0
- package/bin/lib/utils/ssl.js +233 -0
- package/browser-extensions/README.md +1 -0
- package/browser-extensions/chrome/background.js +857 -0
- package/browser-extensions/chrome/content.js +51 -0
- package/browser-extensions/chrome/devtools.html +9 -0
- package/browser-extensions/chrome/devtools.js +23 -0
- package/browser-extensions/chrome/icons/gx_off_128.png +0 -0
- package/browser-extensions/chrome/icons/gx_off_16.png +0 -0
- package/browser-extensions/chrome/icons/gx_off_32.png +0 -0
- package/browser-extensions/chrome/icons/gx_off_64.png +0 -0
- package/browser-extensions/chrome/icons/gx_on_128.png +0 -0
- package/browser-extensions/chrome/icons/gx_on_16.png +0 -0
- package/browser-extensions/chrome/icons/gx_on_32.png +0 -0
- package/browser-extensions/chrome/icons/gx_on_64.png +0 -0
- package/browser-extensions/chrome/inspector.js +1087 -0
- package/browser-extensions/chrome/manifest.json +70 -0
- package/browser-extensions/chrome/panel.html +638 -0
- package/browser-extensions/chrome/panel.js +862 -0
- package/browser-extensions/chrome/popup.html +399 -0
- package/browser-extensions/chrome/popup.js +515 -0
- package/browser-extensions/chrome/rules.json +1 -0
- package/browser-extensions/chrome/test-chrome.html +145 -0
- package/browser-extensions/chrome/test-mixed-content.html +190 -0
- package/browser-extensions/chrome/test-uri-pattern.html +199 -0
- package/browser-extensions/firefox/README.md +134 -0
- package/browser-extensions/firefox/background.js +804 -0
- package/browser-extensions/firefox/content.js +120 -0
- package/browser-extensions/firefox/debug-errors.html +229 -0
- package/browser-extensions/firefox/debug-https.html +113 -0
- package/browser-extensions/firefox/devtools.html +9 -0
- package/browser-extensions/firefox/devtools.js +24 -0
- package/browser-extensions/firefox/icons/gx_off_128.png +0 -0
- package/browser-extensions/firefox/icons/gx_off_16.png +0 -0
- package/browser-extensions/firefox/icons/gx_off_32.png +0 -0
- package/browser-extensions/firefox/icons/gx_off_64.png +0 -0
- package/browser-extensions/firefox/icons/gx_on_128.png +0 -0
- package/browser-extensions/firefox/icons/gx_on_16.png +0 -0
- package/browser-extensions/firefox/icons/gx_on_32.png +0 -0
- package/browser-extensions/firefox/icons/gx_on_64.png +0 -0
- package/browser-extensions/firefox/inspector.js +1087 -0
- package/browser-extensions/firefox/manifest.json +67 -0
- package/browser-extensions/firefox/panel.html +638 -0
- package/browser-extensions/firefox/panel.js +862 -0
- package/browser-extensions/firefox/popup.html +525 -0
- package/browser-extensions/firefox/popup.js +536 -0
- package/browser-extensions/firefox/test-gramercy.html +126 -0
- package/browser-extensions/firefox/test-imports.html +58 -0
- package/browser-extensions/firefox/test-masking.html +147 -0
- package/browser-extensions/firefox/test-uri-pattern.html +199 -0
- package/docs/DOCUSAURUS_IMPORT.md +378 -0
- package/docs/_category_.json +8 -0
- package/docs/app-manifest.md +272 -0
- package/docs/building-for-platform.md +315 -0
- package/docs/dev-tools.md +291 -0
- package/docs/getting-started.md +180 -0
- package/docs/gxp-store.md +305 -0
- package/docs/index.md +44 -0
- package/package.json +77 -0
- package/runtime/PortalContainer.vue +326 -0
- package/runtime/dev-tools/DevToolsModal.vue +217 -0
- package/runtime/dev-tools/LayoutSwitcher.vue +221 -0
- package/runtime/dev-tools/MockDataEditor.vue +621 -0
- package/runtime/dev-tools/SocketSimulator.vue +562 -0
- package/runtime/dev-tools/StoreInspector.vue +644 -0
- package/runtime/dev-tools/index.js +6 -0
- package/runtime/gxpStringsPlugin.js +428 -0
- package/runtime/index.html +22 -0
- package/runtime/main.js +32 -0
- package/runtime/mock-api/auth-middleware.js +97 -0
- package/runtime/mock-api/image-generator.js +221 -0
- package/runtime/mock-api/index.js +197 -0
- package/runtime/mock-api/response-generator.js +394 -0
- package/runtime/mock-api/route-generator.js +323 -0
- package/runtime/mock-api/socket-triggers.js +371 -0
- package/runtime/mock-api/spec-loader.js +300 -0
- package/runtime/server.js +180 -0
- package/runtime/stores/gxpPortalConfigStore.js +554 -0
- package/runtime/stores/index.js +6 -0
- package/runtime/vite-inspector-plugin.js +749 -0
- package/runtime/vite-source-tracker-plugin.js +232 -0
- package/runtime/vite.config.js +402 -0
- package/scripts/launch-chrome.js +90 -0
- package/scripts/pack-chrome.js +91 -0
- package/socket-events/AiSessionMessageCreated.json +18 -0
- package/socket-events/SocialStreamPostCreated.json +24 -0
- package/socket-events/SocialStreamPostVariantCompleted.json +23 -0
- package/template/README.md +332 -0
- package/template/app-manifest.json +32 -0
- package/template/dev-assets/images/avatar-placeholder.png +0 -0
- package/template/dev-assets/images/background-placeholder.jpg +0 -0
- package/template/dev-assets/images/banner-placeholder.jpg +0 -0
- package/template/dev-assets/images/icon-placeholder.png +0 -0
- package/template/dev-assets/images/logo-placeholder.png +0 -0
- package/template/dev-assets/images/product-placeholder.jpg +0 -0
- package/template/dev-assets/images/thumbnail-placeholder.jpg +0 -0
- package/template/env.example +51 -0
- package/template/gitignore +53 -0
- package/template/index.html +22 -0
- package/template/main.js +28 -0
- package/template/src/DemoPage.vue +459 -0
- package/template/src/Plugin.vue +38 -0
- package/template/src/stores/index.js +9 -0
- package/template/src/stores/test-data.json +173 -0
- package/template/theme-layouts/AdditionalStyling.css +0 -0
- package/template/theme-layouts/PrivateLayout.vue +39 -0
- package/template/theme-layouts/PublicLayout.vue +39 -0
- package/template/theme-layouts/SystemLayout.vue +39 -0
- package/template/vite.config.js +333 -0
- package/tsconfig.tui.json +21 -0
- package/vite.config.js +164 -0
|
@@ -0,0 +1,428 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GxP Strings Plugin for Vue 3
|
|
3
|
+
*
|
|
4
|
+
* Provides directives for auto-replacement of content from the GxP store:
|
|
5
|
+
*
|
|
6
|
+
* gxp-string - Replace text content with value from stringsList (default)
|
|
7
|
+
* <h1 gxp-string="welcome_title">Welcome</h1>
|
|
8
|
+
*
|
|
9
|
+
* gxp-string + gxp-settings - Replace text content with value from pluginVars
|
|
10
|
+
* <span gxp-string="company_name" gxp-settings>Default Company</span>
|
|
11
|
+
*
|
|
12
|
+
* gxp-string + gxp-assets - Replace text content with value from assetList
|
|
13
|
+
* <span gxp-string="logo_url" gxp-assets>/default/logo.png</span>
|
|
14
|
+
*
|
|
15
|
+
* gxp-string + gxp-state - Replace text content with value from triggerState
|
|
16
|
+
* <span gxp-string="current_status" gxp-state>idle</span>
|
|
17
|
+
*
|
|
18
|
+
* gxp-src - Replace src attribute with value from assetList (default)
|
|
19
|
+
* <img gxp-src="hero_image" src="/dev-assets/placeholder.jpg" />
|
|
20
|
+
*
|
|
21
|
+
* gxp-src + gxp-state - Replace src attribute with value from triggerState
|
|
22
|
+
* <img gxp-src="dynamic_image" gxp-state src="/dev-assets/placeholder.jpg" />
|
|
23
|
+
*
|
|
24
|
+
* The directives will:
|
|
25
|
+
* 1. Store the original value as default
|
|
26
|
+
* 2. Look up the key in the appropriate store
|
|
27
|
+
* 3. Replace content/attribute if a value exists
|
|
28
|
+
* 4. Fall back to the original if no value found
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
import { watch } from "vue";
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Create the GxP Strings Plugin
|
|
35
|
+
* @param {Object} store - The GxP Pinia store instance
|
|
36
|
+
* @returns {Object} Vue plugin
|
|
37
|
+
*/
|
|
38
|
+
export function createGxpStringsPlugin(store) {
|
|
39
|
+
return {
|
|
40
|
+
install(app) {
|
|
41
|
+
// Register the v-gxp-string directive
|
|
42
|
+
app.directive("gxp-string", {
|
|
43
|
+
// Called when the element is mounted
|
|
44
|
+
mounted(el, binding) {
|
|
45
|
+
const key = binding.value || el.getAttribute("gxp-string");
|
|
46
|
+
if (!key) return;
|
|
47
|
+
|
|
48
|
+
// Store the original text content as default
|
|
49
|
+
const defaultValue = el.textContent || "";
|
|
50
|
+
el._gxpStringDefault = defaultValue;
|
|
51
|
+
el._gxpStringKey = key;
|
|
52
|
+
|
|
53
|
+
// Initial update
|
|
54
|
+
updateElementText(el, key, defaultValue, store);
|
|
55
|
+
|
|
56
|
+
// Watch for changes in the appropriate store based on attributes
|
|
57
|
+
// Using deep: true to catch when entire object is replaced (async manifest load)
|
|
58
|
+
if (store) {
|
|
59
|
+
const watchSource = getWatchSource(el, key, store);
|
|
60
|
+
if (watchSource) {
|
|
61
|
+
el._gxpUnwatch = watch(watchSource, () => {
|
|
62
|
+
updateElementText(el, key, defaultValue, store);
|
|
63
|
+
}, { deep: true });
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
|
|
68
|
+
// Called when binding value changes
|
|
69
|
+
updated(el, binding) {
|
|
70
|
+
const key = binding.value || el.getAttribute("gxp-string");
|
|
71
|
+
if (!key) return;
|
|
72
|
+
|
|
73
|
+
const defaultValue = el._gxpStringDefault || el.textContent || "";
|
|
74
|
+
updateElementText(el, key, defaultValue, store);
|
|
75
|
+
},
|
|
76
|
+
|
|
77
|
+
// Cleanup when element is unmounted
|
|
78
|
+
unmounted(el) {
|
|
79
|
+
if (el._gxpUnwatch) {
|
|
80
|
+
el._gxpUnwatch();
|
|
81
|
+
}
|
|
82
|
+
delete el._gxpStringDefault;
|
|
83
|
+
delete el._gxpStringKey;
|
|
84
|
+
delete el._gxpUnwatch;
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// Register the v-gxp-src directive for replacing src attributes
|
|
89
|
+
app.directive("gxp-src", {
|
|
90
|
+
// Called when the element is mounted
|
|
91
|
+
mounted(el, binding) {
|
|
92
|
+
const key = binding.value || el.getAttribute("gxp-src");
|
|
93
|
+
if (!key) return;
|
|
94
|
+
|
|
95
|
+
// Store the original src as default
|
|
96
|
+
const defaultSrc = el.getAttribute("src") || "";
|
|
97
|
+
el._gxpSrcDefault = defaultSrc;
|
|
98
|
+
el._gxpSrcKey = key;
|
|
99
|
+
|
|
100
|
+
// Initial update
|
|
101
|
+
updateElementSrc(el, key, defaultSrc, store);
|
|
102
|
+
|
|
103
|
+
// Watch for changes in the appropriate store based on attributes
|
|
104
|
+
// Using deep: true to catch when entire object is replaced (async manifest load)
|
|
105
|
+
if (store) {
|
|
106
|
+
const watchSource = getSrcWatchSource(el, key, store);
|
|
107
|
+
if (watchSource) {
|
|
108
|
+
el._gxpSrcUnwatch = watch(watchSource, () => {
|
|
109
|
+
updateElementSrc(el, key, defaultSrc, store);
|
|
110
|
+
}, { deep: true });
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
|
|
115
|
+
// Called when binding value changes
|
|
116
|
+
updated(el, binding) {
|
|
117
|
+
const key = binding.value || el.getAttribute("gxp-src");
|
|
118
|
+
if (!key) return;
|
|
119
|
+
|
|
120
|
+
const defaultSrc = el._gxpSrcDefault || el.getAttribute("src") || "";
|
|
121
|
+
updateElementSrc(el, key, defaultSrc, store);
|
|
122
|
+
},
|
|
123
|
+
|
|
124
|
+
// Cleanup when element is unmounted
|
|
125
|
+
unmounted(el) {
|
|
126
|
+
if (el._gxpSrcUnwatch) {
|
|
127
|
+
el._gxpSrcUnwatch();
|
|
128
|
+
}
|
|
129
|
+
delete el._gxpSrcDefault;
|
|
130
|
+
delete el._gxpSrcKey;
|
|
131
|
+
delete el._gxpSrcUnwatch;
|
|
132
|
+
},
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
// Also handle raw gxp-string and gxp-src attributes (without v- prefix)
|
|
136
|
+
// This runs after mount to catch all elements with the attribute
|
|
137
|
+
// We also set up a watcher for manifestLoaded to re-process when manifest loads
|
|
138
|
+
app.mixin({
|
|
139
|
+
mounted() {
|
|
140
|
+
this.$nextTick(() => {
|
|
141
|
+
processGxpStringAttributes(this.$el, store);
|
|
142
|
+
processGxpSrcAttributes(this.$el, store);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// Watch for manifest loading to re-process attributes
|
|
146
|
+
if (store && store.manifestLoaded !== undefined) {
|
|
147
|
+
const rootEl = this.$el;
|
|
148
|
+
this._gxpManifestUnwatch = watch(
|
|
149
|
+
() => store.manifestLoaded,
|
|
150
|
+
(loaded) => {
|
|
151
|
+
if (loaded) {
|
|
152
|
+
this.$nextTick(() => {
|
|
153
|
+
reprocessGxpStringAttributes(rootEl, store);
|
|
154
|
+
reprocessGxpSrcAttributes(rootEl, store);
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
updated() {
|
|
162
|
+
this.$nextTick(() => {
|
|
163
|
+
processGxpStringAttributes(this.$el, store);
|
|
164
|
+
processGxpSrcAttributes(this.$el, store);
|
|
165
|
+
});
|
|
166
|
+
},
|
|
167
|
+
unmounted() {
|
|
168
|
+
if (this._gxpManifestUnwatch) {
|
|
169
|
+
this._gxpManifestUnwatch();
|
|
170
|
+
}
|
|
171
|
+
},
|
|
172
|
+
});
|
|
173
|
+
},
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Get the appropriate watch source for gxp-string based on element attributes
|
|
179
|
+
* We watch for both:
|
|
180
|
+
* 1. The specific key value changing (for DevTools updates)
|
|
181
|
+
* 2. The entire object changing (for manifest loading)
|
|
182
|
+
*
|
|
183
|
+
* By returning the specific value along with the object reference,
|
|
184
|
+
* Vue's watch will trigger when either changes.
|
|
185
|
+
*/
|
|
186
|
+
function getWatchSource(el, key, store) {
|
|
187
|
+
if (el.hasAttribute("gxp-state") && store.triggerState !== undefined) {
|
|
188
|
+
// Watch specific key AND entire object to catch both types of updates
|
|
189
|
+
return () => ({
|
|
190
|
+
value: store.triggerState?.[key],
|
|
191
|
+
obj: store.triggerState,
|
|
192
|
+
loaded: store.manifestLoaded
|
|
193
|
+
});
|
|
194
|
+
} else if (el.hasAttribute("gxp-settings") && store.pluginVars !== undefined) {
|
|
195
|
+
return () => ({
|
|
196
|
+
value: store.pluginVars?.[key],
|
|
197
|
+
obj: store.pluginVars,
|
|
198
|
+
loaded: store.manifestLoaded
|
|
199
|
+
});
|
|
200
|
+
} else if (el.hasAttribute("gxp-assets") && store.assetList !== undefined) {
|
|
201
|
+
return () => ({
|
|
202
|
+
value: store.assetList?.[key],
|
|
203
|
+
obj: store.assetList,
|
|
204
|
+
loaded: store.manifestLoaded
|
|
205
|
+
});
|
|
206
|
+
} else if (store.stringsList !== undefined) {
|
|
207
|
+
return () => ({
|
|
208
|
+
value: store.stringsList?.[key],
|
|
209
|
+
obj: store.stringsList,
|
|
210
|
+
loaded: store.manifestLoaded
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
return null;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Get the appropriate watch source for gxp-src based on element attributes
|
|
218
|
+
* We watch for both the specific key and the entire object.
|
|
219
|
+
*/
|
|
220
|
+
function getSrcWatchSource(el, key, store) {
|
|
221
|
+
if (el.hasAttribute("gxp-state") && store.triggerState !== undefined) {
|
|
222
|
+
return () => ({
|
|
223
|
+
value: store.triggerState?.[key],
|
|
224
|
+
obj: store.triggerState,
|
|
225
|
+
loaded: store.manifestLoaded
|
|
226
|
+
});
|
|
227
|
+
} else if (store.assetList !== undefined) {
|
|
228
|
+
return () => ({
|
|
229
|
+
value: store.assetList?.[key],
|
|
230
|
+
obj: store.assetList,
|
|
231
|
+
loaded: store.manifestLoaded
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
return null;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Update element text content based on store value
|
|
239
|
+
*/
|
|
240
|
+
function updateElementText(el, key, defaultValue, store) {
|
|
241
|
+
if (!store) {
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
let translatedValue;
|
|
246
|
+
if (el.hasAttribute("gxp-state")) {
|
|
247
|
+
translatedValue = store.getState?.(key);
|
|
248
|
+
} else if (el.hasAttribute("gxp-settings")) {
|
|
249
|
+
translatedValue = store.getSetting?.(key);
|
|
250
|
+
} else if (el.hasAttribute("gxp-assets")) {
|
|
251
|
+
translatedValue = store.getAsset?.(key);
|
|
252
|
+
} else {
|
|
253
|
+
translatedValue = store.getString?.(key);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if (translatedValue && translatedValue !== defaultValue) {
|
|
257
|
+
el.textContent = translatedValue;
|
|
258
|
+
} else {
|
|
259
|
+
el.textContent = defaultValue;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Update element src attribute based on store value
|
|
265
|
+
* Uses triggerState if gxp-state attribute is present, otherwise assetList
|
|
266
|
+
*/
|
|
267
|
+
function updateElementSrc(el, key, defaultSrc, store) {
|
|
268
|
+
if (!store) {
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
let srcUrl;
|
|
273
|
+
if (el.hasAttribute("gxp-state")) {
|
|
274
|
+
srcUrl = store.getState?.(key);
|
|
275
|
+
} else {
|
|
276
|
+
srcUrl = store.getAsset?.(key);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if (srcUrl && srcUrl !== defaultSrc) {
|
|
280
|
+
el.setAttribute("src", srcUrl);
|
|
281
|
+
} else if (defaultSrc) {
|
|
282
|
+
el.setAttribute("src", defaultSrc);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Process all elements with gxp-string attribute in a subtree
|
|
288
|
+
* This handles elements that use the raw attribute without the v- directive
|
|
289
|
+
*/
|
|
290
|
+
function processGxpStringAttributes(rootEl, store) {
|
|
291
|
+
if (!rootEl || !store) return;
|
|
292
|
+
|
|
293
|
+
// Handle case where rootEl is a text node or comment
|
|
294
|
+
if (!rootEl.querySelectorAll) return;
|
|
295
|
+
|
|
296
|
+
const elements = rootEl.querySelectorAll("[gxp-string]");
|
|
297
|
+
|
|
298
|
+
elements.forEach((el) => {
|
|
299
|
+
const key = el.getAttribute("gxp-string");
|
|
300
|
+
if (!key) return;
|
|
301
|
+
|
|
302
|
+
// Store default value only on first processing
|
|
303
|
+
if (!el._gxpStringDefault) {
|
|
304
|
+
el._gxpStringDefault = el.textContent || "";
|
|
305
|
+
}
|
|
306
|
+
el._gxpStringKey = key;
|
|
307
|
+
|
|
308
|
+
// Update text
|
|
309
|
+
updateElementText(el, key, el._gxpStringDefault, store);
|
|
310
|
+
|
|
311
|
+
// Set up watcher for this element (if not already watching)
|
|
312
|
+
if (!el._gxpUnwatch) {
|
|
313
|
+
const watchSource = getWatchSource(el, key, store);
|
|
314
|
+
if (watchSource) {
|
|
315
|
+
el._gxpUnwatch = watch(watchSource, () => {
|
|
316
|
+
updateElementText(el, key, el._gxpStringDefault, store);
|
|
317
|
+
}, { deep: true });
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Re-process all elements with gxp-string attribute in a subtree
|
|
325
|
+
* This is called when manifest loads to update elements that were already processed
|
|
326
|
+
*/
|
|
327
|
+
function reprocessGxpStringAttributes(rootEl, store) {
|
|
328
|
+
if (!rootEl || !store) return;
|
|
329
|
+
if (!rootEl.querySelectorAll) return;
|
|
330
|
+
|
|
331
|
+
const elements = rootEl.querySelectorAll("[gxp-string]");
|
|
332
|
+
|
|
333
|
+
elements.forEach((el) => {
|
|
334
|
+
const key = el.getAttribute("gxp-string");
|
|
335
|
+
if (!key) return;
|
|
336
|
+
|
|
337
|
+
const defaultValue = el._gxpStringDefault || el.textContent || "";
|
|
338
|
+
updateElementText(el, key, defaultValue, store);
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Process all elements with gxp-src attribute in a subtree
|
|
344
|
+
* This handles elements that use the raw attribute without the v- directive
|
|
345
|
+
*/
|
|
346
|
+
function processGxpSrcAttributes(rootEl, store) {
|
|
347
|
+
if (!rootEl || !store) return;
|
|
348
|
+
|
|
349
|
+
// Handle case where rootEl is a text node or comment
|
|
350
|
+
if (!rootEl.querySelectorAll) return;
|
|
351
|
+
|
|
352
|
+
const elements = rootEl.querySelectorAll("[gxp-src]");
|
|
353
|
+
|
|
354
|
+
elements.forEach((el) => {
|
|
355
|
+
const key = el.getAttribute("gxp-src");
|
|
356
|
+
if (!key) return;
|
|
357
|
+
|
|
358
|
+
// Store default value only on first processing
|
|
359
|
+
if (!el._gxpSrcDefault) {
|
|
360
|
+
el._gxpSrcDefault = el.getAttribute("src") || "";
|
|
361
|
+
}
|
|
362
|
+
el._gxpSrcKey = key;
|
|
363
|
+
|
|
364
|
+
// Update src
|
|
365
|
+
updateElementSrc(el, key, el._gxpSrcDefault, store);
|
|
366
|
+
|
|
367
|
+
// Set up watcher for this element (if not already watching)
|
|
368
|
+
if (!el._gxpSrcUnwatch) {
|
|
369
|
+
const watchSource = getSrcWatchSource(el, key, store);
|
|
370
|
+
if (watchSource) {
|
|
371
|
+
el._gxpSrcUnwatch = watch(watchSource, () => {
|
|
372
|
+
updateElementSrc(el, key, el._gxpSrcDefault, store);
|
|
373
|
+
}, { deep: true });
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* Re-process all elements with gxp-src attribute in a subtree
|
|
381
|
+
* This is called when manifest loads to update elements that were already processed
|
|
382
|
+
*/
|
|
383
|
+
function reprocessGxpSrcAttributes(rootEl, store) {
|
|
384
|
+
if (!rootEl || !store) return;
|
|
385
|
+
if (!rootEl.querySelectorAll) return;
|
|
386
|
+
|
|
387
|
+
const elements = rootEl.querySelectorAll("[gxp-src]");
|
|
388
|
+
|
|
389
|
+
elements.forEach((el) => {
|
|
390
|
+
const key = el.getAttribute("gxp-src");
|
|
391
|
+
if (!key) return;
|
|
392
|
+
|
|
393
|
+
const defaultSrc = el._gxpSrcDefault || el.getAttribute("src") || "";
|
|
394
|
+
updateElementSrc(el, key, defaultSrc, store);
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* Standalone function to process gxp-string attributes
|
|
400
|
+
* Can be called manually if needed
|
|
401
|
+
*/
|
|
402
|
+
export function processGxpStrings(rootElement, store) {
|
|
403
|
+
processGxpStringAttributes(rootElement, store);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* Standalone function to process gxp-src attributes
|
|
408
|
+
* Can be called manually if needed
|
|
409
|
+
*/
|
|
410
|
+
export function processGxpSrcs(rootElement, store) {
|
|
411
|
+
processGxpSrcAttributes(rootElement, store);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/**
|
|
415
|
+
* Get the string key from an element if it has gxp-string attribute
|
|
416
|
+
*/
|
|
417
|
+
export function getGxpStringKey(element) {
|
|
418
|
+
return element?.getAttribute?.("gxp-string") || null;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* Check if an element has a gxp-string attribute
|
|
423
|
+
*/
|
|
424
|
+
export function hasGxpString(element) {
|
|
425
|
+
return element?.hasAttribute?.("gxp-string") || false;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
export default createGxpStringsPlugin;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
|
|
4
|
+
<head>
|
|
5
|
+
<meta charset="UTF-8" />
|
|
6
|
+
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
|
7
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
8
|
+
<title>GxP Plugin Builder</title>
|
|
9
|
+
<script type="module">
|
|
10
|
+
import * as Vue from '/node_modules/vue/dist/vue.esm-bundler.js';
|
|
11
|
+
window.Vue = Vue;
|
|
12
|
+
import * as Pinia from '/node_modules/pinia/dist/pinia.esm-browser.js';
|
|
13
|
+
window.Pinia = Pinia;
|
|
14
|
+
</script>
|
|
15
|
+
</head>
|
|
16
|
+
|
|
17
|
+
<body>
|
|
18
|
+
<div id="app"></div>
|
|
19
|
+
<script type="module" src="/@gx-runtime/main.js"></script>
|
|
20
|
+
</body>
|
|
21
|
+
|
|
22
|
+
</html>
|
package/runtime/main.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { createApp } from "vue";
|
|
2
|
+
import * as Vue from "vue";
|
|
3
|
+
import * as Pinia from "pinia";
|
|
4
|
+
import { createPinia, setActivePinia } from "pinia";
|
|
5
|
+
|
|
6
|
+
// Create and configure Pinia before any store imports
|
|
7
|
+
const pinia = createPinia();
|
|
8
|
+
setActivePinia(pinia);
|
|
9
|
+
|
|
10
|
+
// Expose Vue and Pinia to window for dynamically loaded plugins
|
|
11
|
+
window.Vue = Vue;
|
|
12
|
+
window.Pinia = Pinia;
|
|
13
|
+
window.pinia = pinia;
|
|
14
|
+
|
|
15
|
+
// Dynamic imports ensure pinia is set up before stores load
|
|
16
|
+
async function init() {
|
|
17
|
+
const { default: App } = await import("@gx-runtime/PortalContainer.vue");
|
|
18
|
+
const { useGxpStore } = await import("@/stores/index.js");
|
|
19
|
+
const { createGxpStringsPlugin } = await import("@gx-runtime/gxpStringsPlugin.js");
|
|
20
|
+
|
|
21
|
+
window.useGxpStore = useGxpStore;
|
|
22
|
+
|
|
23
|
+
const app = createApp(App);
|
|
24
|
+
app.use(pinia);
|
|
25
|
+
|
|
26
|
+
const gxpStore = useGxpStore();
|
|
27
|
+
app.use(createGxpStringsPlugin(gxpStore));
|
|
28
|
+
|
|
29
|
+
app.mount("#app");
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
init();
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auth Middleware
|
|
3
|
+
*
|
|
4
|
+
* Validates Bearer token format (not signature) for mock API requests.
|
|
5
|
+
* This simulates authentication without actually verifying tokens.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Validate Bearer token format
|
|
10
|
+
* @param {object} req - Express request
|
|
11
|
+
* @param {object} res - Express response
|
|
12
|
+
* @param {function} next - Next middleware
|
|
13
|
+
*/
|
|
14
|
+
function authMiddleware(req, res, next) {
|
|
15
|
+
const auth = req.headers.authorization;
|
|
16
|
+
|
|
17
|
+
// Check if Authorization header exists
|
|
18
|
+
if (!auth) {
|
|
19
|
+
return res.status(401).json({
|
|
20
|
+
error: "Unauthorized",
|
|
21
|
+
message: "Missing Authorization header",
|
|
22
|
+
hint: "Include 'Authorization: Bearer <token>' header",
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Check Bearer token format
|
|
27
|
+
if (!auth.startsWith("Bearer ")) {
|
|
28
|
+
return res.status(401).json({
|
|
29
|
+
error: "Unauthorized",
|
|
30
|
+
message: "Invalid Authorization format",
|
|
31
|
+
hint: "Use 'Bearer <token>' format",
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Extract token
|
|
36
|
+
const token = auth.slice(7).trim();
|
|
37
|
+
|
|
38
|
+
// Check token is not empty
|
|
39
|
+
if (!token) {
|
|
40
|
+
return res.status(401).json({
|
|
41
|
+
error: "Unauthorized",
|
|
42
|
+
message: "Empty token",
|
|
43
|
+
hint: "Provide a non-empty token after 'Bearer '",
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Token format is valid, attach to request for potential use
|
|
48
|
+
req.mockToken = token;
|
|
49
|
+
|
|
50
|
+
// Continue to next middleware/handler
|
|
51
|
+
next();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Optional auth middleware - allows requests without auth but attaches token if present
|
|
56
|
+
* @param {object} req - Express request
|
|
57
|
+
* @param {object} res - Express response
|
|
58
|
+
* @param {function} next - Next middleware
|
|
59
|
+
*/
|
|
60
|
+
function optionalAuthMiddleware(req, res, next) {
|
|
61
|
+
const auth = req.headers.authorization;
|
|
62
|
+
|
|
63
|
+
if (auth && auth.startsWith("Bearer ")) {
|
|
64
|
+
req.mockToken = auth.slice(7).trim();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
next();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Create auth middleware based on OpenAPI security requirements
|
|
72
|
+
* @param {object} operation - OpenAPI operation object
|
|
73
|
+
* @returns {function} Middleware function
|
|
74
|
+
*/
|
|
75
|
+
function createAuthMiddleware(operation) {
|
|
76
|
+
// Check if operation has security requirements
|
|
77
|
+
const security = operation.security;
|
|
78
|
+
|
|
79
|
+
// If security is explicitly empty array, no auth required
|
|
80
|
+
if (Array.isArray(security) && security.length === 0) {
|
|
81
|
+
return optionalAuthMiddleware;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// If security is defined, require auth
|
|
85
|
+
if (security && security.length > 0) {
|
|
86
|
+
return authMiddleware;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Default: require auth (safer default for mock API)
|
|
90
|
+
return authMiddleware;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
module.exports = {
|
|
94
|
+
authMiddleware,
|
|
95
|
+
optionalAuthMiddleware,
|
|
96
|
+
createAuthMiddleware,
|
|
97
|
+
};
|