docs: update addon API documentation and examples
Add comprehensive documentation for: - Event subscription API with examples - License status implementation for commercial addons - Header actions configuration for view panels - View communication using postMessage instead of bridge - Clarify activate() must return true for successful activation Update demo and settings views to reflect new communication patterns.
This commit is contained in:
228
README.md
228
README.md
@@ -92,8 +92,11 @@ class MyAddon {
|
||||
// Called once when addon is loaded
|
||||
async initialize(context) { }
|
||||
|
||||
// Called when addon is enabled
|
||||
async activate() { }
|
||||
// Called when addon is enabled - MUST return true for successful activation
|
||||
async activate() {
|
||||
// ... setup code ...
|
||||
return true;
|
||||
}
|
||||
|
||||
// Called when addon is disabled
|
||||
async deactivate() { }
|
||||
@@ -103,6 +106,8 @@ class MyAddon {
|
||||
}
|
||||
```
|
||||
|
||||
**Important**: The `activate()` method must return `true` to indicate successful activation. If it returns `undefined` or `false`, the addon will fail to activate.
|
||||
|
||||
### Context Object
|
||||
|
||||
The `context` object provides:
|
||||
@@ -113,12 +118,62 @@ context.addonPath // Path to addon directory
|
||||
context.manifest // Parsed addon.json
|
||||
context.log // Logger (.debug, .info, .warn, .error)
|
||||
context.settings // Settings storage
|
||||
context.events // Event emitter
|
||||
context.events // Event subscription
|
||||
context.ipc // IPC communication
|
||||
context.appState // Access to app state
|
||||
context.hostPort // Port of native host (if configured)
|
||||
```
|
||||
|
||||
### Event Subscription
|
||||
|
||||
Subscribe to app events via `context.events`:
|
||||
|
||||
```javascript
|
||||
// Repository changed
|
||||
const disposable = context.events.onRepositorySelected((repo) => {
|
||||
console.log('Repository:', repo.path);
|
||||
});
|
||||
|
||||
// Commit created
|
||||
context.events.onCommitCreated((commit) => {
|
||||
console.log('New commit:', commit.sha);
|
||||
});
|
||||
|
||||
// Files changed
|
||||
context.events.onFilesChanged((files) => {
|
||||
console.log('Files changed:', files.length);
|
||||
});
|
||||
|
||||
// App ready
|
||||
context.events.onAppReady(() => {
|
||||
console.log('App is ready');
|
||||
});
|
||||
|
||||
// App will quit
|
||||
context.events.onAppWillQuit(() => {
|
||||
// Clean up resources
|
||||
});
|
||||
|
||||
// Remember to dispose when done
|
||||
disposable.dispose();
|
||||
```
|
||||
|
||||
### License Status (Optional)
|
||||
|
||||
For commercial addons, implement `getLicenseStatus()`:
|
||||
|
||||
```javascript
|
||||
getLicenseStatus() {
|
||||
// For free addons, always return valid
|
||||
return { valid: true };
|
||||
|
||||
// For commercial addons:
|
||||
// return {
|
||||
// valid: this.isLicenseValid,
|
||||
// message: this.isLicenseValid ? undefined : 'License expired'
|
||||
// };
|
||||
}
|
||||
```
|
||||
|
||||
### Invoke Method
|
||||
|
||||
GitCaddy calls your addon's `invoke` method for all actions:
|
||||
@@ -182,12 +237,27 @@ Position options: `start`, `end`, `after-push-pull`, `after-branch`
|
||||
"type": "iframe",
|
||||
"source": "views/my-view.html"
|
||||
},
|
||||
"context": ["repository"]
|
||||
"context": ["repository"],
|
||||
"headerActions": [
|
||||
{
|
||||
"id": "refresh",
|
||||
"label": "Refresh",
|
||||
"icon": "🔄",
|
||||
"primary": true
|
||||
},
|
||||
{
|
||||
"id": "settings",
|
||||
"label": "Settings",
|
||||
"icon": "⚙️"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Context options: `repository`, `diff`, `commit`, `always`
|
||||
|
||||
**Header Actions**: Buttons displayed in GitCaddy's view header bar (next to the close button). Use this instead of creating your own header inside the HTML view. The `primary` property highlights the button.
|
||||
|
||||
### Context Menu Items
|
||||
|
||||
```json
|
||||
@@ -219,30 +289,144 @@ Types: `boolean`, `string`, `number`, `select`
|
||||
|
||||
## View Communication
|
||||
|
||||
Views communicate with the addon via the `window.gitcaddy` bridge:
|
||||
Views (iframes) communicate with GitCaddy via `postMessage`:
|
||||
|
||||
### Receiving Messages from GitCaddy
|
||||
|
||||
```javascript
|
||||
// Wait for bridge to be ready
|
||||
if (window.gitcaddy) {
|
||||
init();
|
||||
} else {
|
||||
window.addEventListener('gitcaddy-ready', init);
|
||||
const ADDON_ID = 'com.example.myfirst-addon';
|
||||
|
||||
window.addEventListener('message', (event) => {
|
||||
const { type, data, actionId, requestId, result, error } = event.data || {};
|
||||
|
||||
switch (type) {
|
||||
case 'addon:init-context':
|
||||
// Received initial context (repositoryPath, hostPort)
|
||||
const { repositoryPath, hostPort } = data;
|
||||
initializeView();
|
||||
break;
|
||||
|
||||
case 'addon:header-action':
|
||||
// Header button was clicked
|
||||
handleHeaderAction(actionId);
|
||||
break;
|
||||
|
||||
case 'addon:invoke-response':
|
||||
// Response from an invoke request
|
||||
handleInvokeResponse(requestId, result, error);
|
||||
break;
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### Invoking Addon Methods
|
||||
|
||||
```javascript
|
||||
// Helper for invoke with Promise support
|
||||
const pendingRequests = new Map();
|
||||
let requestIdCounter = 0;
|
||||
|
||||
function invokeAddon(method, ...args) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const requestId = ++requestIdCounter;
|
||||
pendingRequests.set(requestId, { resolve, reject });
|
||||
|
||||
window.parent.postMessage({
|
||||
type: 'addon:invoke',
|
||||
requestId,
|
||||
addonId: ADDON_ID,
|
||||
method,
|
||||
args
|
||||
}, '*');
|
||||
|
||||
// Timeout after 30 seconds
|
||||
setTimeout(() => {
|
||||
if (pendingRequests.has(requestId)) {
|
||||
pendingRequests.delete(requestId);
|
||||
reject(new Error('Request timed out'));
|
||||
}
|
||||
}, 30000);
|
||||
});
|
||||
}
|
||||
|
||||
// Invoke addon methods
|
||||
const result = await window.gitcaddy.invoke('myMethod', arg1, arg2);
|
||||
// Usage
|
||||
const data = await invokeAddon('getDemoData');
|
||||
```
|
||||
|
||||
// Send one-way messages
|
||||
window.gitcaddy.send('my-channel', { data: 'value' });
|
||||
### Other Messages to GitCaddy
|
||||
|
||||
// Listen for messages
|
||||
window.gitcaddy.on('addon-message', (data) => {
|
||||
console.log('Received:', data);
|
||||
});
|
||||
```javascript
|
||||
// Open addon settings dialog
|
||||
window.parent.postMessage({
|
||||
type: 'addon:open-settings',
|
||||
addonId: ADDON_ID
|
||||
}, '*');
|
||||
|
||||
// Logging
|
||||
window.gitcaddy.log.info('Something happened');
|
||||
window.gitcaddy.log.error('Something went wrong');
|
||||
// Refresh toolbar badge
|
||||
window.parent.postMessage({
|
||||
type: 'addon:refresh-badge',
|
||||
addonId: ADDON_ID,
|
||||
buttonId: 'my-button',
|
||||
data: { count: 5 }
|
||||
}, '*');
|
||||
```
|
||||
|
||||
## Dark Mode Styling
|
||||
|
||||
GitCaddy injects CSS variables for theming. Use them to match the app's appearance:
|
||||
|
||||
### Theme Colors
|
||||
|
||||
```css
|
||||
/* Background colors */
|
||||
background-color: var(--background, #1e1e1e);
|
||||
background-color: var(--background-secondary, #252526);
|
||||
|
||||
/* Text colors */
|
||||
color: var(--foreground, #cccccc);
|
||||
color: var(--foreground-muted, #999999);
|
||||
|
||||
/* Border */
|
||||
border-color: var(--border, #3c3c3c);
|
||||
|
||||
/* Accent colors */
|
||||
color: var(--accent, #0e639c);
|
||||
color: var(--success, #4ec9b0);
|
||||
color: var(--warning, #fcba03);
|
||||
color: var(--error, #f14c4c);
|
||||
color: var(--info, #75beff);
|
||||
```
|
||||
|
||||
### Dark Mode Scrollbars
|
||||
|
||||
Always style scrollbars to match dark mode:
|
||||
|
||||
```css
|
||||
/* Webkit browsers (Chrome, Safari, Edge) */
|
||||
::-webkit-scrollbar {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: var(--background-secondary, #252526);
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: var(--scrollbar-thumb, #5a5a5a);
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--scrollbar-thumb-hover, #7a7a7a);
|
||||
}
|
||||
|
||||
/* Firefox */
|
||||
* {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: var(--scrollbar-thumb, #5a5a5a) var(--background-secondary, #252526);
|
||||
}
|
||||
```
|
||||
|
||||
## Native Hosts
|
||||
|
||||
18
addon.json
18
addon.json
@@ -114,7 +114,23 @@
|
||||
"source": "views/demo.html"
|
||||
},
|
||||
"_comment_context": "When to show: repository, diff, commit, always",
|
||||
"context": ["repository"]
|
||||
"context": ["repository"],
|
||||
"_comment_headerActions": "Buttons shown in GitCaddy's view header bar (next to the X close button)",
|
||||
"_comment_headerActions_note": "Use this instead of creating your own header inside the HTML view",
|
||||
"headerActions": [
|
||||
{
|
||||
"id": "refresh",
|
||||
"label": "Refresh",
|
||||
"icon": "🔄",
|
||||
"_comment_primary": "Primary buttons are highlighted",
|
||||
"primary": true
|
||||
},
|
||||
{
|
||||
"id": "settings",
|
||||
"label": "Settings",
|
||||
"icon": "⚙️"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
|
||||
|
||||
@@ -76,13 +76,14 @@ class MyFirstAddon {
|
||||
this.disposables.push(settingsDisposable);
|
||||
|
||||
// Register for repository events
|
||||
const repoDisposable = context.events.on('repository-changed', (repo) => {
|
||||
// Note: Use the specific event methods, not a generic 'on' method
|
||||
const repoDisposable = context.events.onRepositorySelected((repo) => {
|
||||
this.onRepositoryChanged(repo);
|
||||
});
|
||||
this.disposables.push(repoDisposable);
|
||||
|
||||
// Register for commit events
|
||||
const commitDisposable = context.events.on('commit-created', (commit) => {
|
||||
const commitDisposable = context.events.onCommitCreated((commit) => {
|
||||
this.onCommitCreated(commit);
|
||||
});
|
||||
this.disposables.push(commitDisposable);
|
||||
@@ -104,6 +105,7 @@ class MyFirstAddon {
|
||||
// this.startPeriodicTask();
|
||||
|
||||
this.context?.log.info('MyFirstAddon activated');
|
||||
return true; // Must return true to indicate successful activation
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -209,6 +211,30 @@ class MyFirstAddon {
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// LICENSE METHODS
|
||||
// Optional: Implement if your addon has licensing
|
||||
// ============================================================
|
||||
|
||||
/**
|
||||
* Get the license status for this addon.
|
||||
*
|
||||
* GitCaddy calls this method to check if the addon is licensed.
|
||||
* If not implemented, the addon is assumed to be licensed (free addon).
|
||||
*
|
||||
* @returns {{ valid: boolean, message?: string }}
|
||||
*/
|
||||
getLicenseStatus() {
|
||||
// For free/open-source addons, always return valid
|
||||
return { valid: true };
|
||||
|
||||
// For commercial addons, you would check license here:
|
||||
// return {
|
||||
// valid: this.isLicenseValid,
|
||||
// message: this.isLicenseValid ? undefined : 'License expired'
|
||||
// };
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// SETTINGS METHODS
|
||||
// ============================================================
|
||||
@@ -489,6 +515,16 @@ class MyFirstAddon {
|
||||
* @property {function} onDidChange - Register for setting changes
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} IAddonEvents
|
||||
* @property {function} onAppReady - Called when app is ready
|
||||
* @property {function} onAppWillQuit - Called before app quits
|
||||
* @property {function} onRepositorySelected - Called when repository changes
|
||||
* @property {function} onFilesChanged - Called when files change
|
||||
* @property {function} onCommitCreated - Called when a commit is created
|
||||
* @property {function} emit - Emit a custom event
|
||||
*/
|
||||
|
||||
// Export the addon class as default
|
||||
module.exports = MyFirstAddon;
|
||||
module.exports.default = MyFirstAddon;
|
||||
|
||||
276
views/demo.html
276
views/demo.html
@@ -16,6 +16,10 @@
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
html, body {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
/* Use GitCaddy's theme colors */
|
||||
font-family: var(--font-family, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif);
|
||||
@@ -24,6 +28,43 @@
|
||||
background-color: var(--background, #1e1e1e);
|
||||
padding: 16px;
|
||||
line-height: 1.5;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
DARK MODE SCROLLBAR STYLING
|
||||
Use theme colors for scrollbars to match dark mode
|
||||
============================================================ */
|
||||
|
||||
/* Webkit browsers (Chrome, Safari, Edge) */
|
||||
::-webkit-scrollbar {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: var(--background-secondary, #252526);
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: var(--scrollbar-thumb, #5a5a5a);
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--scrollbar-thumb-hover, #7a7a7a);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-corner {
|
||||
background: var(--background-secondary, #252526);
|
||||
}
|
||||
|
||||
/* Firefox */
|
||||
* {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: var(--scrollbar-thumb, #5a5a5a) var(--background-secondary, #252526);
|
||||
}
|
||||
|
||||
h1, h2, h3 {
|
||||
@@ -221,10 +262,28 @@
|
||||
|
||||
.tab-content {
|
||||
display: none;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.tab-content.active {
|
||||
display: block;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* Make the logs section fill available space */
|
||||
#logs .section {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
#logs .log-output {
|
||||
flex: 1;
|
||||
max-height: none;
|
||||
min-height: 100px;
|
||||
}
|
||||
|
||||
.loading {
|
||||
@@ -376,60 +435,158 @@
|
||||
<button class="btn btn-secondary" onclick="clearLogs()">
|
||||
Clear Logs
|
||||
</button>
|
||||
<button class="btn" onclick="copyLogs()">
|
||||
📋 Copy Logs
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ============================================================
|
||||
SCRIPTS
|
||||
Communication with GitCaddy happens via window.gitcaddy
|
||||
Communication with GitCaddy happens via postMessage
|
||||
============================================================ -->
|
||||
<script>
|
||||
// ============================================================
|
||||
// GITCADDY BRIDGE
|
||||
// GitCaddy injects a bridge object for communication
|
||||
// ADDON CONFIGURATION
|
||||
// ============================================================
|
||||
const ADDON_ID = 'com.example.myfirst-addon';
|
||||
|
||||
// Context received from GitCaddy
|
||||
let repositoryPath = null;
|
||||
let hostPort = null;
|
||||
|
||||
// Pending invoke requests (for async responses)
|
||||
const pendingRequests = new Map();
|
||||
let requestIdCounter = 0;
|
||||
|
||||
// ============================================================
|
||||
// INVOKE HELPER
|
||||
// Invoke addon methods via postMessage
|
||||
// ============================================================
|
||||
|
||||
/**
|
||||
* The GitCaddy bridge provides:
|
||||
* - gitcaddy.invoke(method, ...args) - Call addon main process methods
|
||||
* - gitcaddy.send(channel, data) - Send one-way message to main
|
||||
* - gitcaddy.on(channel, callback) - Listen for messages from main
|
||||
* - gitcaddy.log.info/warn/error/debug() - Log messages
|
||||
*
|
||||
* The bridge is injected automatically when the view loads.
|
||||
*/
|
||||
function invokeAddon(method, ...args) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const requestId = ++requestIdCounter;
|
||||
pendingRequests.set(requestId, { resolve, reject });
|
||||
|
||||
// Wait for the bridge to be ready
|
||||
function whenReady(callback) {
|
||||
if (window.gitcaddy) {
|
||||
callback();
|
||||
} else {
|
||||
window.addEventListener('gitcaddy-ready', callback);
|
||||
window.parent.postMessage({
|
||||
type: 'addon:invoke',
|
||||
requestId,
|
||||
addonId: ADDON_ID,
|
||||
method,
|
||||
args
|
||||
}, '*');
|
||||
|
||||
// Timeout after 30 seconds
|
||||
setTimeout(() => {
|
||||
if (pendingRequests.has(requestId)) {
|
||||
pendingRequests.delete(requestId);
|
||||
reject(new Error('Request timed out'));
|
||||
}
|
||||
}, 30000);
|
||||
});
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// MESSAGE HANDLER
|
||||
// Handle messages from GitCaddy
|
||||
// ============================================================
|
||||
|
||||
window.addEventListener('message', (event) => {
|
||||
const { type, data, actionId, requestId, result, error } = event.data || {};
|
||||
|
||||
switch (type) {
|
||||
case 'addon:init-context':
|
||||
// Received initial context from GitCaddy
|
||||
repositoryPath = data?.repositoryPath;
|
||||
hostPort = data?.hostPort;
|
||||
log('info', 'Received context from GitCaddy');
|
||||
initializeView();
|
||||
break;
|
||||
|
||||
case 'addon:header-action':
|
||||
// Header button was clicked
|
||||
log('info', `Header action clicked: ${actionId}`);
|
||||
handleHeaderAction(actionId);
|
||||
break;
|
||||
|
||||
case 'addon:invoke-response':
|
||||
// Response from an invoke request
|
||||
if (pendingRequests.has(requestId)) {
|
||||
const { resolve, reject } = pendingRequests.get(requestId);
|
||||
pendingRequests.delete(requestId);
|
||||
if (error) {
|
||||
reject(new Error(error));
|
||||
} else {
|
||||
resolve(result);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
// ============================================================
|
||||
// HEADER ACTIONS
|
||||
// Handle header button clicks from addon.json -> headerActions
|
||||
// ============================================================
|
||||
|
||||
function handleHeaderAction(actionId) {
|
||||
switch (actionId) {
|
||||
case 'refresh':
|
||||
refreshData();
|
||||
break;
|
||||
case 'settings':
|
||||
openSettings();
|
||||
break;
|
||||
default:
|
||||
log('warn', `Unknown header action: ${actionId}`);
|
||||
}
|
||||
}
|
||||
|
||||
function openSettings() {
|
||||
window.parent.postMessage({
|
||||
type: 'addon:open-settings',
|
||||
addonId: ADDON_ID
|
||||
}, '*');
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// INITIALIZATION
|
||||
// ============================================================
|
||||
|
||||
whenReady(async () => {
|
||||
async function initializeView() {
|
||||
log('info', 'Demo view initialized');
|
||||
|
||||
// Load addon data
|
||||
try {
|
||||
const data = await window.gitcaddy.invoke('getDemoData');
|
||||
const data = await invokeAddon('getDemoData');
|
||||
updateDisplay(data);
|
||||
log('info', 'Loaded addon data');
|
||||
} catch (err) {
|
||||
log('error', 'Failed to load addon data: ' + err.message);
|
||||
// Show fallback data
|
||||
updateDisplay({
|
||||
addonId: ADDON_ID,
|
||||
addonPath: 'Loading...',
|
||||
isActive: true,
|
||||
settings: {},
|
||||
features: ['Toolbar buttons', 'Menu items', 'Context menus', 'Custom views', 'Settings storage', 'IPC communication', 'Event handling']
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Listen for messages from main process
|
||||
window.gitcaddy.on('addon-message', (data) => {
|
||||
log('info', 'Received message: ' + JSON.stringify(data));
|
||||
});
|
||||
});
|
||||
// Refresh data when header Refresh button is clicked
|
||||
async function refreshData() {
|
||||
log('info', 'Refreshing addon data...');
|
||||
try {
|
||||
const data = await invokeAddon('getDemoData');
|
||||
updateDisplay(data);
|
||||
log('info', 'Data refreshed successfully');
|
||||
} catch (err) {
|
||||
log('error', 'Failed to refresh: ' + err.message);
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// DISPLAY UPDATES
|
||||
@@ -451,19 +608,20 @@
|
||||
// Update settings
|
||||
const settingsEl = document.getElementById('settings-display');
|
||||
if (data.settings) {
|
||||
settingsEl.innerHTML = Object.entries(data.settings)
|
||||
const settingsHtml = Object.entries(data.settings)
|
||||
.filter(([key]) => key !== 'apiKey') // Don't show API key
|
||||
.map(([key, value]) => `
|
||||
<span class="info-label">${key}:</span>
|
||||
<span class="info-value">${JSON.stringify(value)}</span>
|
||||
<span class="info-label">${escapeHtml(key)}:</span>
|
||||
<span class="info-value">${escapeHtml(JSON.stringify(value))}</span>
|
||||
`).join('');
|
||||
settingsEl.innerHTML = settingsHtml || '<span class="info-label">No settings configured</span>';
|
||||
}
|
||||
|
||||
// Update features list
|
||||
const featuresEl = document.getElementById('features-list');
|
||||
if (data.features && data.features.length > 0) {
|
||||
featuresEl.innerHTML = data.features
|
||||
.map(f => `<li>${f}</li>`)
|
||||
.map(f => `<li>${escapeHtml(f)}</li>`)
|
||||
.join('');
|
||||
}
|
||||
}
|
||||
@@ -497,7 +655,7 @@
|
||||
log('info', `Performing action: ${actionType}`);
|
||||
|
||||
try {
|
||||
const result = await window.gitcaddy.invoke('performAction', actionType);
|
||||
const result = await invokeAddon('performAction', actionType);
|
||||
resultEl.textContent = JSON.stringify(result, null, 2);
|
||||
log('info', `Action result: ${JSON.stringify(result)}`);
|
||||
} catch (err) {
|
||||
@@ -513,20 +671,19 @@
|
||||
function sendMessage() {
|
||||
const resultEl = document.getElementById('ipc-result');
|
||||
|
||||
if (!window.gitcaddy) {
|
||||
resultEl.textContent = 'Error: GitCaddy bridge not available';
|
||||
return;
|
||||
}
|
||||
// Send a one-way message (just demonstrates the pattern)
|
||||
// Note: For iframes, use invokeAddon for two-way communication
|
||||
log('info', 'Sending demo message via invoke...');
|
||||
|
||||
// Send a one-way message
|
||||
window.gitcaddy.send('demo-message', {
|
||||
type: 'greeting',
|
||||
message: 'Hello from the demo view!',
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
|
||||
resultEl.textContent = 'Message sent! Check main process logs.';
|
||||
log('info', 'Sent message to main process');
|
||||
invokeAddon('performAction', 'log')
|
||||
.then(result => {
|
||||
resultEl.textContent = 'Message sent! Result: ' + JSON.stringify(result, null, 2);
|
||||
log('info', 'Message sent successfully');
|
||||
})
|
||||
.catch(err => {
|
||||
resultEl.textContent = 'Error: ' + err.message;
|
||||
log('error', 'Send message failed: ' + err.message);
|
||||
});
|
||||
}
|
||||
|
||||
async function invokeMethod() {
|
||||
@@ -535,7 +692,7 @@
|
||||
log('info', 'Invoking getDemoData method');
|
||||
|
||||
try {
|
||||
const result = await window.gitcaddy.invoke('getDemoData');
|
||||
const result = await invokeAddon('getDemoData');
|
||||
resultEl.textContent = JSON.stringify(result, null, 2);
|
||||
log('info', 'Method invocation successful');
|
||||
} catch (err) {
|
||||
@@ -562,11 +719,6 @@
|
||||
|
||||
logEl.appendChild(entry);
|
||||
logEl.scrollTop = logEl.scrollHeight;
|
||||
|
||||
// Also log to GitCaddy's logger if available
|
||||
if (window.gitcaddy?.log) {
|
||||
window.gitcaddy.log[level]?.(message);
|
||||
}
|
||||
}
|
||||
|
||||
function clearLogs() {
|
||||
@@ -579,11 +731,35 @@
|
||||
`;
|
||||
}
|
||||
|
||||
function copyLogs() {
|
||||
const logEl = document.getElementById('log-output');
|
||||
const logEntries = logEl.querySelectorAll('.log-entry');
|
||||
const logText = Array.from(logEntries).map(entry => {
|
||||
const time = entry.querySelector('.log-time')?.textContent || '';
|
||||
const message = entry.querySelector('span:last-child')?.textContent || '';
|
||||
return `${time} ${message}`;
|
||||
}).join('\n');
|
||||
|
||||
navigator.clipboard.writeText(logText)
|
||||
.then(() => {
|
||||
log('info', 'Logs copied to clipboard!');
|
||||
})
|
||||
.catch(err => {
|
||||
log('error', 'Failed to copy logs: ' + err.message);
|
||||
});
|
||||
}
|
||||
|
||||
function escapeHtml(text) {
|
||||
const div = document.createElement('div');
|
||||
div.textContent = text;
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// STARTUP
|
||||
// Initialize immediately in case context was already sent
|
||||
// ============================================================
|
||||
log('info', 'Demo view loading...');
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -25,6 +25,40 @@
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
DARK MODE SCROLLBAR STYLING
|
||||
============================================================ */
|
||||
|
||||
/* Webkit browsers (Chrome, Safari, Edge) */
|
||||
::-webkit-scrollbar {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: var(--background-secondary, #252526);
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: var(--scrollbar-thumb, #5a5a5a);
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--scrollbar-thumb-hover, #7a7a7a);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-corner {
|
||||
background: var(--background-secondary, #252526);
|
||||
}
|
||||
|
||||
/* Firefox */
|
||||
* {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: var(--scrollbar-thumb, #5a5a5a) var(--background-secondary, #252526);
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 1.4em;
|
||||
margin-bottom: 8px;
|
||||
|
||||
Reference in New Issue
Block a user