feat(plugins): expose plugin routes and load FurryPlace SDK
Add setupPluginRoutes to the server and register it in index.ts. Include /furryplace-sdk.js on admin, index and moderation backup pages to enable custom button extensions.
This commit is contained in:
@@ -88,6 +88,9 @@
|
||||
href="./img/apple-touch-icon.png"
|
||||
/>
|
||||
<link rel="manifest" href="./site.webmanifest" />
|
||||
|
||||
<!-- FurryPlace SDK for custom button extensions -->
|
||||
<script src="/furryplace-sdk.js"></script>
|
||||
</head>
|
||||
|
||||
<body data-sveltekit-preload-data="hover">
|
||||
|
||||
@@ -0,0 +1,345 @@
|
||||
/**
|
||||
* FurryPlace SDK - Button Extension API
|
||||
*
|
||||
* This SDK allows external scripts to add custom buttons to the FurryPlace UI.
|
||||
*
|
||||
* @example
|
||||
* // Register a custom button
|
||||
* window.FurryPlaceSDK.registerButton({
|
||||
* id: 'my-custom-button',
|
||||
* title: 'My Button',
|
||||
* icon: '<svg>...</svg>',
|
||||
* position: 'before-leaderboard', // or 'after-leaderboard', 'top', 'bottom'
|
||||
* onClick: (context) => {
|
||||
* console.log('Button clicked!', context);
|
||||
* },
|
||||
* condition: (context) => {
|
||||
* // Optional: only show when certain conditions are met
|
||||
* return context.user?.isLoggedIn;
|
||||
* }
|
||||
* });
|
||||
*/
|
||||
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
const SDK_VERSION = '1.0.0';
|
||||
const registeredButtons = [];
|
||||
let isInitialized = false;
|
||||
let injectionPoint = null;
|
||||
|
||||
// Find the button container in the DOM
|
||||
function findButtonContainer() {
|
||||
// Look for the container with class "flex flex-col items-center gap-3"
|
||||
const containers = document.querySelectorAll('.flex.flex-col.items-center.gap-3');
|
||||
for (const container of containers) {
|
||||
// Verify it contains the expected buttons by checking for specific classes
|
||||
const hasExpectedButtons = container.querySelector('.btn.btn-square.shadow-md');
|
||||
if (hasExpectedButtons) {
|
||||
return container;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Wait for the DOM to contain the button container
|
||||
function waitForButtonContainer(callback, maxAttempts = 50) {
|
||||
let attempts = 0;
|
||||
const interval = setInterval(() => {
|
||||
const container = findButtonContainer();
|
||||
if (container) {
|
||||
clearInterval(interval);
|
||||
callback(container);
|
||||
} else if (++attempts >= maxAttempts) {
|
||||
clearInterval(interval);
|
||||
console.error('[FurryPlace SDK] Could not find button container after', maxAttempts, 'attempts');
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
|
||||
// Create a button element
|
||||
function createButton(config) {
|
||||
const wrapper = document.createElement('div');
|
||||
wrapper.setAttribute('data-furryplace-button', config.id);
|
||||
wrapper.className = config.wrapperClass || '';
|
||||
|
||||
const button = document.createElement('button');
|
||||
button.className = config.className || 'btn btn-square shadow-md';
|
||||
button.title = config.title || '';
|
||||
|
||||
if (config.disabled) {
|
||||
button.disabled = true;
|
||||
}
|
||||
|
||||
// Add icon
|
||||
if (config.icon) {
|
||||
if (typeof config.icon === 'string') {
|
||||
button.innerHTML = config.icon;
|
||||
} else if (config.icon instanceof HTMLElement) {
|
||||
button.appendChild(config.icon);
|
||||
}
|
||||
}
|
||||
|
||||
// Add click handler
|
||||
if (config.onClick) {
|
||||
button.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
const context = getContext();
|
||||
config.onClick(context, e);
|
||||
});
|
||||
}
|
||||
|
||||
wrapper.appendChild(button);
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
// Get context information for button callbacks
|
||||
function getContext() {
|
||||
return {
|
||||
sdk: {
|
||||
version: SDK_VERSION,
|
||||
},
|
||||
user: {
|
||||
// This would need to be populated from the app's state
|
||||
// For now, return null - plugins can access app state directly if needed
|
||||
isLoggedIn: false,
|
||||
},
|
||||
map: {
|
||||
// Map context if available
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Inject buttons into the DOM
|
||||
function injectButtons(container) {
|
||||
if (!container) return;
|
||||
|
||||
registeredButtons.forEach(config => {
|
||||
// Check condition
|
||||
if (config.condition) {
|
||||
const context = getContext();
|
||||
if (!config.condition(context)) {
|
||||
return; // Skip this button
|
||||
}
|
||||
}
|
||||
|
||||
const button = createButton(config);
|
||||
|
||||
// Determine insertion position
|
||||
switch (config.position) {
|
||||
case 'top':
|
||||
container.insertBefore(button, container.firstChild);
|
||||
break;
|
||||
case 'bottom':
|
||||
container.appendChild(button);
|
||||
break;
|
||||
case 'before-leaderboard': {
|
||||
// Find leaderboard button (has specific SVG viewBox)
|
||||
const leaderboardBtn = Array.from(container.querySelectorAll('button')).find(btn => {
|
||||
const svg = btn.querySelector('svg[viewBox="0 -960 960 960"]');
|
||||
return svg && svg.querySelector('path[d*="160-200h160v-320"]');
|
||||
});
|
||||
if (leaderboardBtn && leaderboardBtn.parentElement) {
|
||||
container.insertBefore(button, leaderboardBtn.parentElement);
|
||||
} else {
|
||||
container.appendChild(button);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'after-leaderboard': {
|
||||
const leaderboardBtn = Array.from(container.querySelectorAll('button')).find(btn => {
|
||||
const svg = btn.querySelector('svg[viewBox="0 -960 960 960"]');
|
||||
return svg && svg.querySelector('path[d*="160-200h160v-320"]');
|
||||
});
|
||||
if (leaderboardBtn && leaderboardBtn.parentElement && leaderboardBtn.parentElement.nextSibling) {
|
||||
container.insertBefore(button, leaderboardBtn.parentElement.nextSibling);
|
||||
} else {
|
||||
container.appendChild(button);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
container.appendChild(button);
|
||||
}
|
||||
});
|
||||
|
||||
injectionPoint = container;
|
||||
}
|
||||
|
||||
// Re-inject buttons (useful when the app re-renders)
|
||||
function reinjectButtons() {
|
||||
if (!injectionPoint) return;
|
||||
|
||||
// Remove old injected buttons
|
||||
const oldButtons = injectionPoint.querySelectorAll('[data-furryplace-button]');
|
||||
oldButtons.forEach(btn => btn.remove());
|
||||
|
||||
// Re-inject
|
||||
injectButtons(injectionPoint);
|
||||
}
|
||||
|
||||
// Initialize the SDK
|
||||
function initialize() {
|
||||
if (isInitialized) {
|
||||
console.warn('[FurryPlace SDK] Already initialized');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('[FurryPlace SDK] Initializing v' + SDK_VERSION);
|
||||
|
||||
waitForButtonContainer((container) => {
|
||||
console.log('[FurryPlace SDK] Found button container, injecting buttons...');
|
||||
injectButtons(container);
|
||||
isInitialized = true;
|
||||
|
||||
// Set up mutation observer to re-inject on DOM changes
|
||||
const observer = new MutationObserver(() => {
|
||||
const currentContainer = findButtonContainer();
|
||||
if (currentContainer && currentContainer !== injectionPoint) {
|
||||
// Container changed, re-inject
|
||||
injectButtons(currentContainer);
|
||||
} else if (currentContainer && injectionPoint) {
|
||||
// Check if our buttons are still there
|
||||
const ourButtons = currentContainer.querySelectorAll('[data-furryplace-button]');
|
||||
if (ourButtons.length !== registeredButtons.length) {
|
||||
reinjectButtons();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
observer.observe(document.body, {
|
||||
childList: true,
|
||||
subtree: true,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Public API
|
||||
window.FurryPlaceSDK = {
|
||||
version: SDK_VERSION,
|
||||
|
||||
/**
|
||||
* Register a custom button
|
||||
* @param {Object} config - Button configuration
|
||||
* @param {string} config.id - Unique identifier for the button
|
||||
* @param {string} config.title - Tooltip text
|
||||
* @param {string|HTMLElement} config.icon - SVG string or element
|
||||
* @param {Function} config.onClick - Click handler (context, event) => {}
|
||||
* @param {string} [config.position='bottom'] - Where to place the button
|
||||
* @param {string} [config.className] - Custom CSS classes
|
||||
* @param {string} [config.wrapperClass] - Wrapper div CSS classes
|
||||
* @param {Function} [config.condition] - Optional condition (context) => boolean
|
||||
* @param {boolean} [config.disabled=false] - Whether button is disabled
|
||||
*/
|
||||
registerButton(config) {
|
||||
if (!config.id) {
|
||||
console.error('[FurryPlace SDK] Button must have an id');
|
||||
return;
|
||||
}
|
||||
|
||||
if (registeredButtons.find(b => b.id === config.id)) {
|
||||
console.error('[FurryPlace SDK] Button with id "' + config.id + '" already registered');
|
||||
return;
|
||||
}
|
||||
|
||||
registeredButtons.push(config);
|
||||
console.log('[FurryPlace SDK] Registered button:', config.id);
|
||||
|
||||
// If already initialized, inject immediately
|
||||
if (isInitialized && injectionPoint) {
|
||||
const button = createButton(config);
|
||||
injectionPoint.appendChild(button);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Unregister a button
|
||||
* @param {string} id - Button ID to remove
|
||||
*/
|
||||
unregisterButton(id) {
|
||||
const index = registeredButtons.findIndex(b => b.id === id);
|
||||
if (index !== -1) {
|
||||
registeredButtons.splice(index, 1);
|
||||
|
||||
// Remove from DOM
|
||||
if (injectionPoint) {
|
||||
const element = injectionPoint.querySelector('[data-furryplace-button="' + id + '"]');
|
||||
if (element) {
|
||||
element.remove();
|
||||
}
|
||||
}
|
||||
|
||||
console.log('[FurryPlace SDK] Unregistered button:', id);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Get list of registered buttons
|
||||
*/
|
||||
getButtons() {
|
||||
return registeredButtons.map(b => ({ id: b.id, title: b.title }));
|
||||
},
|
||||
|
||||
/**
|
||||
* Manually trigger SDK initialization
|
||||
*/
|
||||
init() {
|
||||
initialize();
|
||||
},
|
||||
|
||||
/**
|
||||
* Get current context
|
||||
*/
|
||||
getContext() {
|
||||
return getContext();
|
||||
}
|
||||
};
|
||||
|
||||
// Auto-load plugins from server
|
||||
function autoLoadPlugins() {
|
||||
console.log('[FurryPlace SDK] Auto-discovering plugins...');
|
||||
|
||||
fetch('/api/plugins')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (!data.plugins || data.plugins.length === 0) {
|
||||
console.log('[FurryPlace SDK] No plugins found');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('[FurryPlace SDK] Found ' + data.plugins.length + ' plugin(s):', data.plugins.map(p => p.name));
|
||||
|
||||
// Load each plugin dynamically
|
||||
data.plugins.forEach(plugin => {
|
||||
const script = document.createElement('script');
|
||||
script.src = plugin.path;
|
||||
script.async = true;
|
||||
script.onerror = () => {
|
||||
console.error('[FurryPlace SDK] Failed to load plugin:', plugin.name);
|
||||
};
|
||||
script.onload = () => {
|
||||
console.log('[FurryPlace SDK] Loaded plugin:', plugin.name);
|
||||
};
|
||||
document.head.appendChild(script);
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
console.warn('[FurryPlace SDK] Failed to auto-discover plugins:', error.message);
|
||||
});
|
||||
}
|
||||
|
||||
// Auto-initialize when DOM is ready
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
initialize();
|
||||
autoLoadPlugins();
|
||||
});
|
||||
} else {
|
||||
initialize();
|
||||
autoLoadPlugins();
|
||||
}
|
||||
|
||||
console.log('[FurryPlace SDK] Loaded v' + SDK_VERSION);
|
||||
})();
|
||||
@@ -108,6 +108,9 @@
|
||||
href="./img/apple-touch-icon.png"
|
||||
/>
|
||||
<link rel="manifest" href="./site.webmanifest" />
|
||||
|
||||
<!-- FurryPlace SDK for custom button extensions -->
|
||||
<script src="/furryplace-sdk.js"></script>
|
||||
</head>
|
||||
|
||||
<body data-sveltekit-preload-data="hover">
|
||||
|
||||
@@ -95,6 +95,9 @@
|
||||
href="./img/apple-touch-icon.png"
|
||||
/>
|
||||
<link rel="manifest" href="./site.webmanifest" />
|
||||
|
||||
<!-- FurryPlace SDK for custom button extensions -->
|
||||
<script src="/furryplace-sdk.js"></script>
|
||||
</head>
|
||||
|
||||
<body data-sveltekit-preload-data="hover">
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
# FurryPlace Plugins
|
||||
|
||||
This directory contains plugins that extend the FurryPlace UI using the FurryPlace SDK.
|
||||
|
||||
## Quick Start
|
||||
|
||||
1. **Create a plugin file** in this directory (e.g., `my-plugin.js`)
|
||||
2. **Refresh the page** - Plugins are automatically discovered and loaded!
|
||||
3. **Use the SDK** to register buttons or add other functionality
|
||||
|
||||
> **✨ Auto-Loading:** Plugins are automatically discovered from the `frontend-backup/plugins/` directory. Any `.js` file you add here will be automatically loaded when the page loads. No need to manually edit HTML files!
|
||||
|
||||
## Example Plugin
|
||||
|
||||
See [example-button.js](./example-button.js) for a complete example showing how to:
|
||||
- Add custom buttons with icons
|
||||
- Handle click events
|
||||
- Use conditional rendering
|
||||
- Apply custom styling
|
||||
- Open external links
|
||||
|
||||
## FurryPlace SDK API
|
||||
|
||||
### `window.FurryPlaceSDK.registerButton(config)`
|
||||
|
||||
Register a custom button in the UI.
|
||||
|
||||
**Configuration Object:**
|
||||
|
||||
```javascript
|
||||
{
|
||||
id: string, // Required: Unique identifier
|
||||
title: string, // Required: Tooltip text
|
||||
icon: string|HTMLElement, // Required: SVG string or DOM element
|
||||
onClick: Function, // Required: (context, event) => {}
|
||||
position: string, // Optional: 'top', 'bottom', 'before-leaderboard', 'after-leaderboard'
|
||||
className: string, // Optional: Custom CSS classes (default: 'btn btn-square shadow-md')
|
||||
wrapperClass: string, // Optional: Wrapper div CSS classes
|
||||
condition: Function, // Optional: (context) => boolean
|
||||
disabled: boolean // Optional: Whether button is disabled
|
||||
}
|
||||
```
|
||||
|
||||
**Positions:**
|
||||
- `'top'` - First button in the container
|
||||
- `'bottom'` - Last button in the container
|
||||
- `'before-leaderboard'` - Before the leaderboard button
|
||||
- `'after-leaderboard'` - After the leaderboard button (default)
|
||||
|
||||
**Context Object** (passed to `onClick` and `condition`):
|
||||
|
||||
```javascript
|
||||
{
|
||||
sdk: {
|
||||
version: string // SDK version
|
||||
},
|
||||
user: {
|
||||
isLoggedIn: boolean // User login status (not yet implemented)
|
||||
},
|
||||
map: {} // Map context (not yet implemented)
|
||||
}
|
||||
```
|
||||
|
||||
### Other SDK Methods
|
||||
|
||||
- `FurryPlaceSDK.unregisterButton(id)` - Remove a registered button
|
||||
- `FurryPlaceSDK.getButtons()` - Get list of all registered buttons
|
||||
- `FurryPlaceSDK.getContext()` - Get current context object
|
||||
- `FurryPlaceSDK.init()` - Manually initialize SDK (usually automatic)
|
||||
|
||||
## Example Usage
|
||||
|
||||
```javascript
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
// Wait for SDK to load
|
||||
function waitForSDK(callback) {
|
||||
if (window.FurryPlaceSDK) {
|
||||
callback();
|
||||
} else {
|
||||
setTimeout(() => waitForSDK(callback), 100);
|
||||
}
|
||||
}
|
||||
|
||||
waitForSDK(() => {
|
||||
// Register a button
|
||||
window.FurryPlaceSDK.registerButton({
|
||||
id: 'my-custom-button',
|
||||
title: 'My Button',
|
||||
position: 'bottom',
|
||||
icon: `
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960" fill="currentColor" class="size-5">
|
||||
<path d="M480-80q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Z"/>
|
||||
</svg>
|
||||
`,
|
||||
onClick: (context) => {
|
||||
alert('Button clicked!');
|
||||
console.log('Context:', context);
|
||||
}
|
||||
});
|
||||
|
||||
console.log('[My Plugin] Loaded!');
|
||||
});
|
||||
})();
|
||||
```
|
||||
|
||||
## Finding SVG Icons
|
||||
|
||||
You can find Material Design icons at:
|
||||
- https://fonts.google.com/icons
|
||||
|
||||
Copy the SVG code and use it as the `icon` parameter.
|
||||
|
||||
## Tips
|
||||
|
||||
1. **Wrap your plugin in an IIFE** to avoid polluting the global scope
|
||||
2. **Wait for the SDK** to be available before registering buttons
|
||||
3. **Use unique IDs** to avoid conflicts with other plugins
|
||||
4. **Test button positions** to find the best placement for your use case
|
||||
5. **Check the console** for SDK loading messages and errors
|
||||
|
||||
## How Auto-Loading Works
|
||||
|
||||
The SDK automatically:
|
||||
1. Calls `/api/plugins` to get a list of all `.js` files in the `plugins/` directory
|
||||
2. Dynamically loads each plugin by creating `<script>` tags
|
||||
3. Reports loading progress in the browser console
|
||||
|
||||
You can check which plugins are loaded by looking at the console:
|
||||
```
|
||||
[FurryPlace SDK] Auto-discovering plugins...
|
||||
[FurryPlace SDK] Found 2 plugin(s): ['example-button.js', 'my-plugin.js']
|
||||
[FurryPlace SDK] Loaded plugin: example-button.js
|
||||
[FurryPlace SDK] Loaded plugin: my-plugin.js
|
||||
```
|
||||
|
||||
> **Note:** If you need to disable a plugin, either delete it or rename it to something other than `.js` (like `.js.disabled`)
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**Button not appearing?**
|
||||
- Check the browser console for errors
|
||||
- Verify the SDK is loaded: `console.log(window.FurryPlaceSDK)`
|
||||
- Ensure your button ID is unique
|
||||
- Try different positions
|
||||
|
||||
**Button appearing in wrong place?**
|
||||
- Try different `position` values
|
||||
- Check if the button container has changed in the UI
|
||||
|
||||
**Icon not showing?**
|
||||
- Verify your SVG has `viewBox` and `fill="currentColor"`
|
||||
- Add `class="size-5"` to the SVG element
|
||||
- Check for syntax errors in the SVG string
|
||||
@@ -0,0 +1,122 @@
|
||||
/**
|
||||
* Example FurryPlace Plugin - Custom Button
|
||||
*
|
||||
* This demonstrates how to use the FurryPlace SDK to add custom buttons
|
||||
* to the UI. This plugin adds a "Help" button that opens a dialog.
|
||||
*
|
||||
* To use this plugin, add it to your HTML:
|
||||
* <script src="/plugins/example-button.js"></script>
|
||||
*/
|
||||
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
// Wait for SDK to be available
|
||||
function waitForSDK(callback) {
|
||||
if (window.FurryPlaceSDK) {
|
||||
callback();
|
||||
} else {
|
||||
setTimeout(() => waitForSDK(callback), 100);
|
||||
}
|
||||
}
|
||||
|
||||
// Example 1: Simple button with SVG icon
|
||||
function registerHelpButton() {
|
||||
window.FurryPlaceSDK.registerButton({
|
||||
id: 'example-help',
|
||||
title: 'Help & Documentation',
|
||||
position: 'after-leaderboard',
|
||||
icon: `
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960" fill="currentColor" class="size-5">
|
||||
<path d="M478-240q21 0 35.5-14.5T528-290q0-21-14.5-35.5T478-340q-21 0-35.5 14.5T428-290q0 21 14.5 35.5T478-240Zm-36-154h74q0-33 7.5-52t42.5-52q26-26 41-49.5t15-56.5q0-56-41-86t-97-30q-57 0-92.5 30T342-618l66 26q5-18 22.5-39t53.5-21q32 0 48 17.5t16 38.5q0 20-12 37.5T506-526q-44 39-54 59t-10 73Zm38 314q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-80q134 0 227-93t93-227q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 134 93 227t227 93Zm0-320Z"/>
|
||||
</svg>
|
||||
`,
|
||||
onClick: (context, event) => {
|
||||
alert('Help button clicked!\n\nSDK Version: ' + context.sdk.version);
|
||||
console.log('Context:', context);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Example 2: Button with custom styling
|
||||
function registerDebugButton() {
|
||||
window.FurryPlaceSDK.registerButton({
|
||||
id: 'example-debug',
|
||||
title: 'Debug Info',
|
||||
position: 'bottom',
|
||||
className: 'btn btn-square btn-accent shadow-md', // Custom styling
|
||||
icon: `
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960" fill="currentColor" class="size-5">
|
||||
<path d="M440-280h80v-160h-80v160Zm40-240q17 0 28.5-11.5T520-560q0-17-11.5-28.5T480-600q-17 0-28.5 11.5T440-560q0 17 11.5 28.5T480-520Zm0 440q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-80q134 0 227-93t93-227q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 134 93 227t227 93Zm0-320Z"/>
|
||||
</svg>
|
||||
`,
|
||||
onClick: () => {
|
||||
const info = {
|
||||
buttons: window.FurryPlaceSDK.getButtons(),
|
||||
userAgent: navigator.userAgent,
|
||||
viewport: {
|
||||
width: window.innerWidth,
|
||||
height: window.innerHeight
|
||||
},
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
|
||||
console.log('Debug Info:', info);
|
||||
alert('Debug info logged to console');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Example 3: Conditional button (only shows when user is logged in)
|
||||
function registerConditionalButton() {
|
||||
window.FurryPlaceSDK.registerButton({
|
||||
id: 'example-conditional',
|
||||
title: 'Premium Feature',
|
||||
position: 'before-leaderboard',
|
||||
icon: `
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960" fill="currentColor" class="size-5">
|
||||
<path d="m233-80 65-281L80-550l288-25 112-265 112 265 288 25-218 189 65 281-247-149L233-80Z"/>
|
||||
</svg>
|
||||
`,
|
||||
onClick: () => {
|
||||
alert('This is a premium feature!');
|
||||
},
|
||||
// This condition is just an example - in a real plugin you'd check actual user state
|
||||
condition: (context) => {
|
||||
// For now, always show it (change this to check real user state)
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Example 4: Button that opens an external link
|
||||
function registerDiscordButton() {
|
||||
window.FurryPlaceSDK.registerButton({
|
||||
id: 'example-discord',
|
||||
title: 'Join our Discord',
|
||||
position: 'top',
|
||||
icon: `
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="size-5">
|
||||
<path d="M20.317 4.37a19.791 19.791 0 0 0-4.885-1.515a.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.25a18.27 18.27 0 0 0-5.487 0a12.64 12.64 0 0 0-.617-1.25a.077.077 0 0 0-.079-.037A19.736 19.736 0 0 0 3.677 4.37a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 0 0 .031.057a19.9 19.9 0 0 0 5.993 3.03a.078.078 0 0 0 .084-.028a14.09 14.09 0 0 0 1.226-1.994a.076.076 0 0 0-.041-.106a13.107 13.107 0 0 1-1.872-.892a.077.077 0 0 1-.008-.128a10.2 10.2 0 0 0 .372-.292a.074.074 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.062 0a.074.074 0 0 1 .078.01c.12.098.246.198.373.292a.077.077 0 0 1-.006.127a12.299 12.299 0 0 1-1.873.892a.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028a19.839 19.839 0 0 0 6.002-3.03a.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.03zM8.02 15.33c-1.183 0-2.157-1.085-2.157-2.419c0-1.333.956-2.419 2.157-2.419c1.21 0 2.176 1.096 2.157 2.42c0 1.333-.956 2.418-2.157 2.418zm7.975 0c-1.183 0-2.157-1.085-2.157-2.419c0-1.333.955-2.419 2.157-2.419c1.21 0 2.176 1.096 2.157 2.42c0 1.333-.946 2.418-2.157 2.418z"/>
|
||||
</svg>
|
||||
`,
|
||||
onClick: () => {
|
||||
window.open('https://discord.gg/ZRC4DnP9Z2', '_blank');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Initialize plugin
|
||||
waitForSDK(() => {
|
||||
console.log('[Example Plugin] Initializing...');
|
||||
|
||||
// Register all example buttons
|
||||
registerHelpButton();
|
||||
registerDebugButton();
|
||||
registerConditionalButton();
|
||||
registerDiscordButton();
|
||||
|
||||
console.log('[Example Plugin] Loaded successfully!');
|
||||
console.log('[Example Plugin] Registered buttons:', window.FurryPlaceSDK.getButtons());
|
||||
});
|
||||
})();
|
||||
Reference in New Issue
Block a user