add kirby-loop plugin with French translations
All checks were successful
Deploy / Deploy to Production (push) Successful in 6s

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
isUnknown 2026-03-23 21:41:50 +01:00
parent 8ea5f0c462
commit ab7fd8b2ea
74 changed files with 16423 additions and 2 deletions

View file

@ -0,0 +1,90 @@
---
title: Installation
---
This guide covers all installation methods for the Kirby Loop plugin.
## Prerequisites
Before installing the plugin, ensure your system meets these requirements:
- **Kirby CMS**: Version 4.0 or higher
- **PHP**: Version 8.3 or higher
- **SQLite**: Support enabled (usually included by default in PHP)
## Installation Methods
### Method 1: Composer (Recommended)
Composer is the preferred installation method
```bash
composer require moinframe/kirby-loop
```
### Method 2: Manual Installation
For environments where Composer isn't available or preferred:
1. **Download the plugin**
- Visit the [GitHub releases page](https://github.com/moinframe/kirby-loop/releases)
- Download the latest version as a ZIP file
2. **Extract and place**
- Unzip the downloaded archive
- Rename the folder to `loop` (remove version numbers)
- Move the folder to `/site/plugins/loop`
3. **Verify installation**
- The plugin folder should contain `index.php` and other plugin files
- Your final structure should be: `/site/plugins/loop/index.php`
### Method 3: Git Submodule
For projects using Git version control, submodules provide a clean way to include the plugin:
```bash
git submodule add https://github.com/moinframe/kirby-loop.git site/plugins/loop
```
## Next Steps
After successful installation:
1. **Configuration**: See [Configuration Guide](https://moinfra.me/docs/moinframe-loop/02-configuration) for customization options
2. **Multi-language**: If using multiple languages, review [Multi-language Setup](https://moinfra.me/docs/moinframe-loop/03-multi-language)
3. **API Integration**: For custom implementations, check the [API Reference](https://moinfra.me/docs/moinframe-loop/05-api)
## Updating
### Composer Updates
```bash
composer update moinframe/kirby-loop
```
### Manual Updates
1. Download the new version
2. Replace the plugin folder (backup first!)
3. Clear any caches
### Git Submodule Updates
```bash
git submodule update --remote site/plugins/loop
git add site/plugins/loop
git commit -m "Update loop plugin"
```
## Uninstallation
To remove the plugin:
1. **Remove plugin files**:
- Composer: `composer remove moinframe/kirby-loop`
- Manual: Delete `/site/plugins/loop/` folder
- Git submodule: `git submodule deinit site/plugins/loop`
2. **Clean up data** (optional):
- Delete `/site/logs/loop/` directory to remove all comments
- Remove configuration from `site/config/config.php`
3. **Clear caches**: Clear any site caches to ensure complete removal

View file

@ -0,0 +1,252 @@
---
title: Configuration
---
You can customize the plugin's look and behavior by adding configuration options.
Add configuration options to your `site/config/config.php` file:
```php
<?php
return [
// Your existing Kirby configuration...
// Loop Configuration
'moinframe.loop' => [
'auto-inject' => true,
...
]
];
```
## Configuration Options
### Enable/Disable Tool
**Option**: `moinframe.loop.enabled`
**Type**: `boolean|callable`
**Default**: `true`
Controls whether loop is enabled globally or conditionally.
```php
// Simple boolean enable/disable
'moinframe.loop.enabled' => false, // Disables globally
// Use a callback for dynamic control
'moinframe.loop.enabled' => function($page) {
// Only enable for specific templates
return in_array($page->template()->name(), ['article', 'blog']);
},
// Filter by page status
'moinframe.loop.enabled' => function($page) {
return $page->status() === 'published';
},
// Complex conditions
'moinframe.loop.enabled' => function($page) {
return $page->template()->name() === 'article'
&& $page->status() === 'published'
&& !$page->archived()->toBool();
}
```
**Callback function receives:**
- `$page` - The current Kirby page object
**Common use cases:**
- Disable feedback on specific page templates
- Enable only for published content
- Conditional enabling based on page fields or metadata
**Note**: This option is checked both during auto-injection and manual snippet usage.
### Auto-Injection
**Option**: `moinframe.loop.auto-inject`
**Type**: `boolean`
**Default**: `true`
Controls whether loop is automatically injected into all pages.
```php
// Disable auto-injection (requires manual snippet placement)
'moinframe.loop.auto-inject' => false,
```
When disabled, you must manually add the snippet to your templates:
```php
<?php snippet('loop/app') ?>
```
**Use cases for disabling auto-injection:**
- Custom page templates where you want precise control
- JavaScript-based routing (Swup, Taxi.js) that needs manual initialization
- Conditional loading based on user roles or page types
### Position
**Option**: `moinframe.loop.position`
**Type**: `string`
**Default**: `'top'`
**Values**: `'top'` | `'bottom'`
Sets the position of loop header on the page.
```php
// Position header at bottom of page
'moinframe.loop.position' => 'bottom',
```
### Database Path
**Option**: `moinframe.loop.database`
**Type**: `string|null`
**Default**: `null` (uses `site/logs/loop/comments.sqlite`)
Customize the SQLite database location.
```php
// Custom database path
'moinframe.loop.database' => '/custom/path/comments.sqlite',
// Alternative locations
'moinframe.loop.database' => kirby()->root('content') . '/feedback.sqlite',
'moinframe.loop.database' => '/var/www/data/feedback.sqlite',
```
**Important considerations:**
- Path must be absolute
- Directory must exist and be writable
- Consider backup strategies for custom locations
- Ensure path is outside web root for security
### Public Access
**Option**: `moinframe.loop.public`
**Type**: `boolean`
**Default**: `false`
Controls whether loop requires authentication.
```php
// Allow public access (no authentication required)
'moinframe.loop.public' => true,
```
**Security implications:**
- `false` (default): Only authenticated panel users can see/use the tool
- `true`: Anyone can add comments
**Recommended for public access:**
- Internal staging environments
- Client review sites with controlled access
- Public beta feedback collection
### Language Override
**Option**: `moinframe.loop.language`
**Type**: `string|null`
**Default**: `null` (auto-detect from Kirby)
Force a specific UI language regardless of the current page language.
```php
// Force German UI
'moinframe.loop.language' => 'de',
// Force English UI
'moinframe.loop.language' => 'en',
```
**When to use:**
- Single-language sites with non-English content but English-speaking editors
- Multi-language sites where editors prefer consistent UI language
### Theme
**Option**: `moinframe.loop.theme`
**Type**: `string`
**Default**: `'default'`
**Values**: `'default'` | `'dark'` | custom theme name
Sets the visual theme for the loop interface.
```php
// Use dark theme
'moinframe.loop.theme' => 'dark',
// Use custom theme
'moinframe.loop.theme' => 'custom',
```
**Available themes:**
- `'default'` - Light theme with clean, bright interface
- `'dark'` - Dark theme for low-light environments
- Custom theme names - See [Theming Guide](https://moinfra.me/docs/moinframe-loop/04-theming) for creating custom themes
### Welcome Dialog
The welcome dialog introduces new users to loop functionality.
#### Enable/Disable Welcome Dialog
**Option**: `moinframe.loop.welcome.enabled`
**Type**: `boolean`
**Default**: `true`
```php
// Disable welcome dialog
'moinframe.loop.welcome.enabled' => false,
```
#### Custom Welcome Headline
**Option**: `moinframe.loop.welcome.headline`
**Type**: `string|null`
**Default**: `null` (uses default translation)
```php
// Custom welcome headline
'moinframe.loop.welcome.headline' => 'Welcome to Our Review Tool!',
```
#### Custom Welcome Text
**Option**: `moinframe.loop.welcome.text`
**Type**: `string|null`
**Default**: `null` (uses default translation)
```php
// Custom welcome message
'moinframe.loop.welcome.text' => 'Click anywhere on the page to leave feedback. Use the toggle button to switch between navigation and comment modes.',
```
## Manual Snippet Usage
When auto-injection is disabled, you have full control over when and where loop appears.
### Basic Usage
```php
<?php snippet('loop/app') ?>
```
### Conditional Loading
```php
<?php if ($kirby->user() && $kirby->user()->role()->isAdmin()): ?>
<?php snippet('loop/app') ?>
<?php endif ?>
```
> [!TIPP]
> Manual snippets also respect the `enabled` configuration option. If you've set up conditional enabling via the `enabled` option, you don't need to duplicate that logic in your template - the snippet will automatically check the enabled status.
## Caching Behavior
> [!WARNING]
> Pages with loop automatically have Kirby's page **cache** **disabled**. This is necessary for CSRF token validation and User authentication checks.

View file

@ -0,0 +1,94 @@
---
title: Multi-Language
---
Kirby Loop provides comprehensive support for multi-language Kirby sites, including automatic language detection and customizable UI translations.
## How Multi-Language Support Works
The plugin automatically detects and adapts to your Kirby site's language configuration. No additional configuration is required - the plugin works automatically with Kirby's multi-language setup.
- **Single-language sites**: Uses the en translations
- **Multi-language sites**: Detects the current page language and adapts accordingly
## UI Language Override
### Forcing a Specific UI Language
By default, loop UI adapts to the current page language. You can override this behavior:
```php
// Always show German UI regardless of page language
'moinframe.loop.language' => 'de',
// Always show English UI regardless of page language
'moinframe.loop.language' => 'en',
```
### Use Cases for Language Override
**Consistent Editor Experience:**
```php
// Editors prefer English UI even on German pages
'moinframe.loop.language' => 'en',
```
**Single-Language website with non english content:**
```php
// German content site with German-speaking editors
'moinframe.loop.language' => 'de',
```
## Built-in Translations
The plugin includes complete translations for:
- English (en) - Default
- German (de)
## Custom Translations
### Adding New Languages
To add support for additional languages, create or extend your Kirby language files:
```php
// site/languages/fr.php
<?php
return [
'code' => 'fr',
'default' => false,
'direction' => 'ltr',
'locale' => 'fr_FR',
'name' => 'Français',
'translations' => [
// UI Elements
'moinframe.loop.ui.header.title' => 'Commentaires',
...
]
];
```
### Overriding Existing Translations
Customize existing translations by adding them to your language files:
```php
// site/languages/en.php - Override English defaults
return [
'code' => 'en',
'default' => true,
'translations' => [
'moinframe.loop.ui.header.title' => 'Page Feedback',
'moinframe.loop.ui.comment.placeholder' => 'What needs attention?',
'moinframe.loop.ui.welcome.headline' => 'Welcome to Our Review Tool',
]
];
```
### Translation Key Reference
For a complete list of available translation keys, see the [plugin's index file](https://github.com/moinframe/kirby-loop/blob/main/index.php).

View file

@ -0,0 +1,110 @@
---
title: Theming
---
Kirby Loop comes with built-in theming support, allowing you to customize the visual appearance to match your brand or provide different user experiences. The plugin includes a default (light) theme and a dark theme, with support for creating custom themes.
## Configuration
### Setting a Theme
Configure the theme in your `site/config/config.php`:
```php
return [
// Set theme: 'default', 'dark', or custom theme name
'moinframe.loop.theme' => 'dark',
];
```
**Available options:**
- `'default'` - Light theme (default)
- `'dark'` - Dark theme
- Custom theme name
## Creating Custom Themes
Custom themes are CSS files that override the default color and styling tokens. The theming system uses CSS custom properties (variables) for easy customization.
### Basic Custom Theme
Here's a minimal custom theme example:
```css
/* frontend/src/styles/theme-custom.css */
kirby-loop[theme="custom"] {
/* Accent color */
--color-accent-l: 0.6;
--color-accent-c: 0.15;
--color-accent-h: 280; /* Purple accent */
/* Neutral color lightness values */
--color-neutral-l-0: 0.98;
--color-neutral-l-100: 0.92;
--color-neutral-l-200: 0.86;
--color-neutral-l-300: 0.7;
--color-neutral-l-400: 0.6;
--color-neutral-l-500: 0.5;
--color-neutral-l-600: 0.4;
--color-neutral-l-700: 0.3;
--color-neutral-l-800: 0.15;
--color-neutral-l-900: 0.05;
--color-neutral-l-1000: 0;
}
```
### Configure Your Custom Theme
Set your custom theme in the configuration:
```php
// site/config/config.php
return [
'moinframe.loop.theme' => 'custom',
];
```
## Theme Architecture
### Color System
The theming system uses OKLCH color space for consistent, perceptually uniform colors:
```css
/* Accent colors */
--color-accent-l: 0.7; /* Lightness (0-1) */
--color-accent-c: 0.12; /* Chroma/saturation (0-0.4) */
--color-accent-h: 220; /* Hue (0-360) */
/* Neutral colors */
--color-neutral-l-0: 1; /* Lightest */
--color-neutral-l-100: 0.95;
--color-neutral-l-200: 0.9;
/* ... */
--color-neutral-l-900: 0.05;
--color-neutral-l-1000: 0; /* Darkest */
```
### Advanced Customization
You can override any design token in your custom theme:
```css
kirby-loop[theme="custom"] {
/* Colors */
--color-accent-l: 0.65;
--color-accent-c: 0.18;
--color-accent-h: 15; /* Orange accent */
/* Shadows with custom opacity */
--shadow-s: 0 0.1em 0.25em oklch(var(--color-neutral-l-900) var(--color-neutral-c) var(--color-neutral-h) / 0.15);
/* Custom border radius */
--border-radius: 0.5rem;
--border-radius-rounded: 1rem;
/* Custom fonts */
--font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
}
```

View file

@ -0,0 +1,325 @@
---
title: API Reference
---
Kirby Loop provides a RESTful API for managing comments and feedback. All endpoints include CSRF protection.
## Authentication
All API endpoints require authentication, controlled by the `moinframe.loop.public` configuration option:
- **Default (private)**: Only authenticated Kirby users can access the API
- **Public mode**: Anyone can access the API
## CSRF Protection
All API requests must include a valid CSRF token in the request header:
```javascript
fetch('/loop/comments/page-id', {
headers: {
'X-CSRF-Token': '<csrf-token>'
}
});
```
## Base URL Structure
### Single Language Sites
```
/loop/comments/{pageId}
/loop/comment/new
/loop/comment/reply
/loop/comment/resolve
/loop/comment/unresolve
/loop/guest/name
```
### Multi-Language Sites
```
/{language}/loop/comments/{pageId}
/{language}/loop/comment/new
/{language}/loop/comment/reply
/{language}/loop/comment/resolve
/{language}/loop/comment/unresolve
/{language}/loop/guest/name
```
Where `{language}` is the language code (e.g., `en`, `de`).
## Endpoints
### GET /loop/comments/{pageId}
Retrieve all comments for a specific page.
**Parameters:**
- `pageId` (string): The page ID or 'home' for the homepage
**Response:**
```json
{
"status": "ok",
"comments": [
{
"id": 1,
"author": "John Doe",
"url": "https://example.com/page",
"page": "page-uuid",
"comment": "This needs to be updated",
"selector": ".header h1",
"selectorOffsetX": 10,
"selectorOffsetY": 20,
"pagePositionX": 150,
"pagePositionY": 300,
"timestamp": 1640995200,
"lang": "en",
"status": "OPEN",
"replies": [
{
"id": 1,
"author": "jane.smith",
"comment": "I'll fix this",
"parentId": 1,
"timestamp": 1640995800
}
]
}
]
}
```
**Error Responses:**
- `400`: Page not found
- `401`: Unauthorized (if authentication required)
- `403`: CSRF token invalid
### POST /loop/comment/new
Create a new comment on a page.
**Request Body:**
```json
{
"comment": "This section needs clarification",
"url": "https://example.com/page",
"selector": ".content p:nth-child(3)",
"selectorOffsetX": 15,
"selectorOffsetY": 25,
"pagePositionX": 200,
"pagePositionY": 450,
"pageId": "projects/project-alpha"
}
```
**Required Fields:**
- `comment` (string): The comment text (HTML stripped and sanitized)
- `url` (string): The full URL where the comment was made
- `selector` (string): CSS selector for the commented element
- `selectorOffsetX` (number): X offset within the selected element
- `selectorOffsetY` (number): Y offset within the selected element
- `pagePositionX` (number): X position on the page
- `pagePositionY` (number): Y position on the page
- `pageId` (string): Kirby page ID or 'home'
**Response:**
```json
{
"status": "ok",
"comment": {
"id": 15,
"author": "John Doe",
"url": "https://example.com/page",
"page": "page-uuid",
"comment": "This section needs clarification",
"selector": ".content p:nth-child(3)",
"selectorOffsetX": 15,
"selectorOffsetY": 25,
"pagePositionX": 200,
"pagePositionY": 450,
"timestamp": 1640995200,
"lang": "en",
"status": "OPEN",
"replies": []
}
}
```
**Error Responses:**
- `400`: Missing required fields, invalid selector format, or invalid data
- `401`: Unauthorized
- `403`: CSRF token invalid or disabled
- `404`: Page not found
### POST /loop/comment/reply
Add a reply to an existing comment.
**Request Body:**
```json
{
"comment": "I'll handle this update",
"parentId": 15
}
```
**Required Fields:**
- `comment` (string): The reply text (HTML stripped and sanitized)
- `parentId` (number): ID of the parent comment
**Response:**
```json
{
"status": "ok",
"reply": {
"id": 3,
"author": "John Doe",
"comment": "I'll handle this update",
"parentId": 15,
"timestamp": 1640995800
}
}
```
**Error Responses:**
- `400`: Missing required fields
- `401`: Unauthorized
- `403`: CSRF token invalid or disabled
### POST /loop/comment/resolve
Mark a comment as resolved.
**Request Body:**
```json
{
"id": 15
}
```
**Required Fields:**
- `id` (number): The comment ID to resolve
**Response:**
```json
{
"status": "ok",
"success": true
}
```
**Error Responses:**
- `400`: Missing comment ID
- `401`: Unauthorized
- `403`: CSRF token invalid or disabled
### POST /loop/comment/unresolve
Mark a resolved comment as unresolved.
**Request Body:**
```json
{
"id": 15
}
```
**Required Fields:**
- `id` (number): The comment ID to unresolve
**Response:**
```json
{
"status": "ok",
"success": true
}
```
**Error Responses:**
- `400`: Missing comment ID
- `401`: Unauthorized
- `403`: CSRF token invalid or disabled
### POST /loop/guest/name
Set a guest name for non-authenticated users (when public mode is enabled).
**Request Body:**
```json
{
"name": "John Doe"
}
```
**Required Fields:**
- `name` (string): The guest user's name
**Response:**
```json
{
"status": "ok",
"name": "John Doe"
}
```
**Error Responses:**
- `400`: Missing or empty name
- `401`: Unauthorized
- `403`: CSRF token invalid or disabled
## Data Models
### Comment Object
```typescript
interface Comment {
id: number;
author: string; // Resolved display name (user name, email prefix, or guest name)
url: string; // Full URL where comment was made
page: string; // Page UUID
comment: string; // Sanitized comment text
selector: string; // CSS selector for target element
selectorOffsetX: number; // X offset within element (float)
selectorOffsetY: number; // Y offset within element (float)
pagePositionX: number; // X position on page (float)
pagePositionY: number; // Y position on page (float)
timestamp: number; // Unix timestamp
lang: string; // Language code
status: string; // Status: OPEN, RESOLVED
replies: Reply[]; // Array of replies
}
```
### Reply Object
```typescript
interface Reply {
id: number;
author: string; // Resolved display name (user name, email prefix, or guest name)
comment: string; // Sanitized reply text
parentId: number; // Parent comment ID
timestamp: number; // Unix timestamp
}
```
## Error Handling
The api endpoints return consistent error responses. For more details, switch on the debug mode in your Kirby Installation.
```json
{
"status": "error",
"message": "Human-readable error message",
"code": "ERROR_CODE" // Optional error code
}
```
### Common Error Codes
- `CSRF_INVALID`: CSRF token is missing or invalid
- `PAGE_NOT_FOUND`: Specified page doesn't exist
- `FIELD_REQUIRED`: Required field is missing
- `UNAUTHORIZED`: Authentication required but not provided
- `INVALID_SELECTOR`: Invalid selector format
- `INVALID_NAME`: Invalid guest name
- `DISABLED`: Tool is disabled