Files
logikonline d5be7cc604 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.
2026-01-18 14:03:23 -05:00

621 lines
17 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>My First Addon - Settings</title>
<style>
/* ============================================================
SETTINGS VIEW STYLES
Demonstrates all setting types available in GitCaddy addons
============================================================ */
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: var(--font-family, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif);
font-size: var(--font-size, 13px);
color: var(--foreground, #cccccc);
background-color: var(--background, #1e1e1e);
padding: 16px;
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;
color: var(--foreground, #cccccc);
}
.subtitle {
color: var(--foreground-muted, #999999);
margin-bottom: 24px;
}
/* Form Sections */
.form-section {
background-color: var(--background-secondary, #252526);
border: 1px solid var(--border, #3c3c3c);
border-radius: 6px;
padding: 16px;
margin-bottom: 16px;
}
.form-section h2 {
font-size: 1.1em;
margin-bottom: 16px;
padding-bottom: 8px;
border-bottom: 1px solid var(--border, #3c3c3c);
}
/* Form Groups */
.form-group {
margin-bottom: 16px;
}
.form-group:last-child {
margin-bottom: 0;
}
.form-label {
display: block;
font-weight: 500;
margin-bottom: 6px;
color: var(--foreground, #cccccc);
}
.form-description {
font-size: 12px;
color: var(--foreground-muted, #999999);
margin-top: 4px;
}
/* Input Styles */
.form-input {
width: 100%;
padding: 8px 12px;
font-size: 13px;
font-family: inherit;
color: var(--input-foreground, #cccccc);
background-color: var(--input-background, #3c3c3c);
border: 1px solid var(--input-border, #3c3c3c);
border-radius: 4px;
outline: none;
transition: border-color 0.15s, box-shadow 0.15s;
}
.form-input:focus {
border-color: var(--focus-border, #0e639c);
box-shadow: 0 0 0 1px var(--focus-border, #0e639c);
}
.form-input:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.form-input::placeholder {
color: var(--input-placeholder, #666666);
}
/* Password Input with Toggle */
.input-with-button {
display: flex;
gap: 8px;
}
.input-with-button .form-input {
flex: 1;
}
/* Select Styles */
.form-select {
width: 100%;
padding: 8px 12px;
font-size: 13px;
font-family: inherit;
color: var(--input-foreground, #cccccc);
background-color: var(--input-background, #3c3c3c);
border: 1px solid var(--input-border, #3c3c3c);
border-radius: 4px;
outline: none;
cursor: pointer;
transition: border-color 0.15s;
}
.form-select:focus {
border-color: var(--focus-border, #0e639c);
}
/* Checkbox Styles */
.checkbox-wrapper {
display: flex;
align-items: flex-start;
gap: 10px;
}
.checkbox-wrapper input[type="checkbox"] {
width: 18px;
height: 18px;
margin-top: 2px;
accent-color: var(--accent, #0e639c);
cursor: pointer;
}
.checkbox-wrapper .checkbox-label {
flex: 1;
}
.checkbox-wrapper .checkbox-title {
font-weight: 500;
color: var(--foreground, #cccccc);
}
.checkbox-wrapper .checkbox-description {
font-size: 12px;
color: var(--foreground-muted, #999999);
margin-top: 2px;
}
/* Number Input */
.form-input[type="number"] {
width: 120px;
}
/* Range Slider */
.range-wrapper {
display: flex;
align-items: center;
gap: 12px;
}
.form-range {
flex: 1;
height: 4px;
-webkit-appearance: none;
background: var(--input-background, #3c3c3c);
border-radius: 2px;
outline: none;
}
.form-range::-webkit-slider-thumb {
-webkit-appearance: none;
width: 16px;
height: 16px;
background: var(--accent, #0e639c);
border-radius: 50%;
cursor: pointer;
}
.range-value {
min-width: 40px;
text-align: right;
font-family: var(--font-family-mono, monospace);
color: var(--foreground-muted, #999999);
}
/* Buttons */
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 6px;
padding: 8px 16px;
font-size: 13px;
font-weight: 500;
color: var(--button-foreground, #ffffff);
background-color: var(--button-background, #0e639c);
border: none;
border-radius: 4px;
cursor: pointer;
transition: background-color 0.15s;
}
.btn:hover {
background-color: var(--button-hover-background, #1177bb);
}
.btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.btn-secondary {
background-color: var(--button-secondary-background, #3c3c3c);
color: var(--button-secondary-foreground, #cccccc);
}
.btn-secondary:hover {
background-color: var(--button-secondary-hover-background, #4c4c4c);
}
.btn-small {
padding: 4px 12px;
font-size: 12px;
}
/* Button Group */
.button-group {
display: flex;
gap: 8px;
margin-top: 24px;
padding-top: 16px;
border-top: 1px solid var(--border, #3c3c3c);
}
/* Status Messages */
.status-message {
padding: 8px 12px;
border-radius: 4px;
font-size: 12px;
margin-top: 16px;
display: none;
}
.status-message.success {
display: block;
background-color: var(--success-background, rgba(78, 201, 176, 0.15));
color: var(--success, #4ec9b0);
border: 1px solid var(--success, #4ec9b0);
}
.status-message.error {
display: block;
background-color: var(--error-background, rgba(241, 76, 76, 0.15));
color: var(--error, #f14c4c);
border: 1px solid var(--error, #f14c4c);
}
/* Code Example Section */
.code-example {
background-color: var(--background, #1e1e1e);
border: 1px solid var(--border, #3c3c3c);
border-radius: 4px;
padding: 12px;
font-family: var(--font-family-mono, 'Consolas', 'Monaco', monospace);
font-size: 12px;
overflow-x: auto;
white-space: pre;
}
.code-comment {
color: var(--foreground-muted, #6a9955);
}
.code-key {
color: var(--info, #9cdcfe);
}
.code-string {
color: var(--warning, #ce9178);
}
.code-number {
color: var(--success, #b5cea8);
}
</style>
</head>
<body>
<h1>My First Addon Settings</h1>
<p class="subtitle">Configure your addon preferences below.</p>
<!-- General Settings -->
<div class="form-section">
<h2>General Settings</h2>
<!-- Boolean Setting (Checkbox) -->
<div class="form-group">
<div class="checkbox-wrapper">
<input type="checkbox" id="enableFeature" checked>
<div class="checkbox-label">
<div class="checkbox-title">Enable Feature</div>
<div class="checkbox-description">
Toggle the main functionality of this addon on or off.
</div>
</div>
</div>
</div>
<!-- Select Setting (Dropdown) -->
<div class="form-group">
<label class="form-label" for="mode">Operating Mode</label>
<select id="mode" class="form-select">
<option value="normal">Normal</option>
<option value="advanced">Advanced</option>
<option value="debug">Debug</option>
</select>
<p class="form-description">
Select the operating mode for the addon.
</p>
</div>
</div>
<!-- API Settings -->
<div class="form-section">
<h2>API Configuration</h2>
<!-- String Setting (Password) -->
<div class="form-group">
<label class="form-label" for="apiKey">API Key</label>
<div class="input-with-button">
<input type="password" id="apiKey" class="form-input" placeholder="Enter your API key">
<button class="btn btn-secondary btn-small" onclick="togglePassword()">Show</button>
</div>
<p class="form-description">
Your API key for external service integration. Keep this secret!
</p>
</div>
</div>
<!-- Advanced Settings -->
<div class="form-section">
<h2>Advanced Settings</h2>
<!-- Number Setting -->
<div class="form-group">
<label class="form-label" for="maxItems">Maximum Items</label>
<input type="number" id="maxItems" class="form-input" value="10" min="1" max="100">
<p class="form-description">
Maximum number of items to process (1-100).
</p>
</div>
<!-- Range Slider Example -->
<div class="form-group">
<label class="form-label" for="threshold">Threshold</label>
<div class="range-wrapper">
<input type="range" id="threshold" class="form-range" min="0" max="100" value="50">
<span class="range-value" id="thresholdValue">50</span>
</div>
<p class="form-description">
Sensitivity threshold for processing (0-100).
</p>
</div>
</div>
<!-- Settings Schema Reference -->
<div class="form-section">
<h2>Settings Schema Reference</h2>
<p style="margin-bottom: 12px; color: var(--foreground-muted);">
This is how settings are defined in addon.json:
</p>
<div class="code-example"><span class="code-comment">// In addon.json -> contributes -> settingsDefinitions</span>
[
{
<span class="code-key">"key"</span>: <span class="code-string">"enableFeature"</span>,
<span class="code-key">"type"</span>: <span class="code-string">"boolean"</span>,
<span class="code-key">"default"</span>: <span class="code-number">true</span>,
<span class="code-key">"description"</span>: <span class="code-string">"Enable the main feature"</span>
},
{
<span class="code-key">"key"</span>: <span class="code-string">"mode"</span>,
<span class="code-key">"type"</span>: <span class="code-string">"select"</span>,
<span class="code-key">"default"</span>: <span class="code-string">"normal"</span>,
<span class="code-key">"options"</span>: [
{ <span class="code-key">"label"</span>: <span class="code-string">"Normal"</span>, <span class="code-key">"value"</span>: <span class="code-string">"normal"</span> },
{ <span class="code-key">"label"</span>: <span class="code-string">"Advanced"</span>, <span class="code-key">"value"</span>: <span class="code-string">"advanced"</span> }
]
}
]</div>
</div>
<!-- Action Buttons -->
<div class="button-group">
<button class="btn" onclick="saveSettings()">Save Settings</button>
<button class="btn btn-secondary" onclick="resetSettings()">Reset to Defaults</button>
</div>
<!-- Status Message -->
<div id="statusMessage" class="status-message"></div>
<script>
// ============================================================
// SETTINGS VIEW SCRIPT
// ============================================================
// Default settings (should match addon.json settingsDefinitions)
const DEFAULT_SETTINGS = {
enableFeature: true,
apiKey: '',
mode: 'normal',
maxItems: 10
};
// Current settings
let currentSettings = { ...DEFAULT_SETTINGS };
// ============================================================
// INITIALIZATION
// ============================================================
// Wait for GitCaddy bridge
function whenReady(callback) {
if (window.gitcaddy) {
callback();
} else {
window.addEventListener('gitcaddy-ready', callback);
}
}
whenReady(async () => {
console.log('Settings view initializing...');
// Load settings from storage
try {
const stored = await window.gitcaddy.invoke('getSettings');
if (stored) {
currentSettings = { ...DEFAULT_SETTINGS, ...stored };
// Note: API key might be hidden in the response
if (stored.hasApiKey) {
document.getElementById('apiKey').placeholder = 'API key is set (hidden)';
}
}
updateUI();
console.log('Settings loaded');
} catch (err) {
console.error('Failed to load settings:', err);
showStatus('error', 'Failed to load settings: ' + err.message);
}
// Set up event listeners
setupEventListeners();
});
// ============================================================
// UI UPDATES
// ============================================================
function updateUI() {
document.getElementById('enableFeature').checked = currentSettings.enableFeature;
document.getElementById('mode').value = currentSettings.mode;
// Don't show the actual API key for security
// document.getElementById('apiKey').value = currentSettings.apiKey;
document.getElementById('maxItems').value = currentSettings.maxItems;
}
function setupEventListeners() {
// Range slider value display
const threshold = document.getElementById('threshold');
const thresholdValue = document.getElementById('thresholdValue');
threshold.addEventListener('input', () => {
thresholdValue.textContent = threshold.value;
});
// Auto-save on change (optional - you could remove this)
const inputs = document.querySelectorAll('input, select');
inputs.forEach(input => {
input.addEventListener('change', () => {
// Mark as changed but don't auto-save
console.log(`Setting changed: ${input.id}`);
});
});
}
// ============================================================
// ACTIONS
// ============================================================
async function saveSettings() {
const settings = {
enableFeature: document.getElementById('enableFeature').checked,
mode: document.getElementById('mode').value,
maxItems: parseInt(document.getElementById('maxItems').value, 10)
};
// Only include API key if it was changed (not empty placeholder)
const apiKeyInput = document.getElementById('apiKey');
if (apiKeyInput.value) {
settings.apiKey = apiKeyInput.value;
}
try {
await window.gitcaddy.invoke('updateSettings', settings);
currentSettings = { ...currentSettings, ...settings };
showStatus('success', 'Settings saved successfully!');
console.log('Settings saved:', settings);
// Clear the API key input after saving
if (apiKeyInput.value) {
apiKeyInput.value = '';
apiKeyInput.placeholder = 'API key is set (hidden)';
}
} catch (err) {
showStatus('error', 'Failed to save settings: ' + err.message);
console.error('Failed to save settings:', err);
}
}
async function resetSettings() {
if (!confirm('Are you sure you want to reset all settings to their defaults?')) {
return;
}
try {
await window.gitcaddy.invoke('updateSettings', DEFAULT_SETTINGS);
currentSettings = { ...DEFAULT_SETTINGS };
updateUI();
// Reset API key placeholder
const apiKeyInput = document.getElementById('apiKey');
apiKeyInput.value = '';
apiKeyInput.placeholder = 'Enter your API key';
showStatus('success', 'Settings reset to defaults!');
console.log('Settings reset to defaults');
} catch (err) {
showStatus('error', 'Failed to reset settings: ' + err.message);
console.error('Failed to reset settings:', err);
}
}
function togglePassword() {
const input = document.getElementById('apiKey');
const button = event.target;
if (input.type === 'password') {
input.type = 'text';
button.textContent = 'Hide';
} else {
input.type = 'password';
button.textContent = 'Show';
}
}
// ============================================================
// STATUS MESSAGES
// ============================================================
function showStatus(type, message) {
const statusEl = document.getElementById('statusMessage');
statusEl.textContent = message;
statusEl.className = 'status-message ' + type;
// Auto-hide after 3 seconds
setTimeout(() => {
statusEl.className = 'status-message';
}, 3000);
}
</script>
</body>
</html>