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.
621 lines
17 KiB
HTML
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>
|