diff --git a/.eslintrc.js b/.eslintrc.js
new file mode 100644
index 0000000..d741523
--- /dev/null
+++ b/.eslintrc.js
@@ -0,0 +1,25 @@
+module.exports = {
+ env: {
+ browser: true,
+ es2021: true,
+ node: true,
+ jest: true
+ },
+ extends: [
+ 'eslint:recommended',
+ 'plugin:jest/recommended',
+ 'prettier'
+ ],
+ plugins: ['jest'],
+ parserOptions: {
+ ecmaVersion: 'latest',
+ sourceType: 'module'
+ },
+ rules: {
+ 'no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
+ 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
+ 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
+ 'semi': ['error', 'always'],
+ 'quotes': ['error', 'single']
+ }
+};
diff --git a/.gitignore b/.gitignore
index 54f107e..e020d5c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,11 +2,21 @@
node_modules/
jspm_packages/
+package-lock.json
+
# Optional npm cache directory
.npm
+# IntelliJ IDEA - ignore user-specific files, but keep shared configurations
+.idea/workspace.xml
+.idea/tasks.xml
+.idea/usage.statistics.xml
+.idea/dictionaries
+.idea/shelf
+*.iws
+
+
# Editor directories and files
-.idea/
.vscode/
*.swp
*.swo
@@ -17,4 +27,14 @@ jspm_packages/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
-lerna-debug.log*
\ No newline at end of file
+lerna-debug.log*
+
+# Local DokuWiki instance data
+/dokuwiki-test/
+# DokuWiki development environment data
+/dokuwiki-dev/data/
+
+# Keep Docker Compose files and scripts
+!/dokuwiki-dev/docker-compose.yml
+!/dokuwiki-dev/start.sh
+!/dokuwiki-dev/stop.sh
diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..13566b8
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,8 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Editor-based HTTP Client requests
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
new file mode 100644
index 0000000..03d9549
--- /dev/null
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/jsLinters/eslint.xml b/.idea/jsLinters/eslint.xml
new file mode 100644
index 0000000..541945b
--- /dev/null
+++ b/.idea/jsLinters/eslint.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/runConfigurations/Format.xml b/.idea/runConfigurations/Format.xml
new file mode 100644
index 0000000..946f564
--- /dev/null
+++ b/.idea/runConfigurations/Format.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.idea/runConfigurations/Jest_Coverage.xml b/.idea/runConfigurations/Jest_Coverage.xml
new file mode 100644
index 0000000..30721c4
--- /dev/null
+++ b/.idea/runConfigurations/Jest_Coverage.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.idea/runConfigurations/Jest_Tests.xml b/.idea/runConfigurations/Jest_Tests.xml
new file mode 100644
index 0000000..61063b0
--- /dev/null
+++ b/.idea/runConfigurations/Jest_Tests.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.idea/runConfigurations/Lint.xml b/.idea/runConfigurations/Lint.xml
new file mode 100644
index 0000000..44e9a27
--- /dev/null
+++ b/.idea/runConfigurations/Lint.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.idea/runConfigurations/Start_DokuWiki.xml b/.idea/runConfigurations/Start_DokuWiki.xml
new file mode 100644
index 0000000..2f2d356
--- /dev/null
+++ b/.idea/runConfigurations/Start_DokuWiki.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.idea/runConfigurations/Stop_DokuWiki.xml b/.idea/runConfigurations/Stop_DokuWiki.xml
new file mode 100644
index 0000000..bd6a290
--- /dev/null
+++ b/.idea/runConfigurations/Stop_DokuWiki.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.idea/runConfigurations/Test_Client.xml b/.idea/runConfigurations/Test_Client.xml
new file mode 100644
index 0000000..14cd7c4
--- /dev/null
+++ b/.idea/runConfigurations/Test_Client.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/.prettierrc b/.prettierrc
new file mode 100644
index 0000000..fe7e2a6
--- /dev/null
+++ b/.prettierrc
@@ -0,0 +1,8 @@
+{
+ "semi": true,
+ "singleQuote": true,
+ "tabWidth": 2,
+ "trailingComma": "es5",
+ "printWidth": 100,
+ "bracketSpacing": true
+}
diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md
new file mode 100644
index 0000000..402af2c
--- /dev/null
+++ b/DEVELOPMENT.md
@@ -0,0 +1,207 @@
+# DokuWiki Manager Plugin - Development Guide
+
+## Project Structure
+
+This project uses a modular architecture with separate modules for authentication, API client, and error handling.
+
+```
+dokuwiki-manager-plugin/
+├── src/ # Source code
+│ ├── auth.js # Authentication module
+│ ├── apiClient.js # JSON-RPC client
+│ ├── errorHandler.js # Error handling
+│ └── index.js # Entry point
+├── tests/ # Test suite
+│ ├── auth.test.js # Auth module tests
+│ ├── apiClient.test.js # API client tests
+│ └── errorHandler.test.js # Error handler tests
+├── examples/ # Example usage
+│ └── test-client.js # Example client
+├── dokuwiki-dev/ # Local development environment
+│ ├── docker-compose.yml # Docker configuration
+│ ├── start.sh # Start script
+│ └── stop.sh # Stop script
+├── implementation.js # Main plugin entry point
+└── plugin.json # TypingMind plugin config
+```
+
+## Development Setup
+
+### Prerequisites
+
+- Node.js (v14+)
+- npm (v6+)
+- Docker and Docker Compose (for local DokuWiki)
+
+### First-Time Setup
+
+1. **Clone the repository:**
+ ```bash
+ git clone https://github.com/satwareAG/dokuwiki-manager-plugin.git
+ cd dokuwiki-manager-plugin
+ ```
+
+2. **Install dependencies:**
+ ```bash
+ npm install
+ ```
+
+3. **Start the DokuWiki development environment:**
+ ```bash
+ cd dokuwiki-dev
+ ./start.sh
+ ```
+
+4. **Complete the DokuWiki setup:**
+ - Navigate to http://localhost:8080/install.php
+ - Follow the setup instructions to create an admin user
+ - Make note of your credentials for testing
+
+5. **Update the test client with your credentials:**
+ ```javascript
+ // In examples/test-client.js
+ const config = {
+ wikiUrl: 'http://localhost:8080',
+ username: 'your_username',
+ password: 'your_password',
+ defaultNamespace: ''
+ };
+ ```
+
+### Development Workflow
+
+#### Running Tests
+
+```bash
+# Run all tests
+npm test
+
+# Watch mode
+npm run test:watch
+
+# With coverage report
+npm run test:coverage
+```
+
+#### Code Quality
+
+```bash
+# Lint code
+npm run lint
+
+# Format code
+npm run format
+```
+
+#### Testing with DokuWiki
+
+```bash
+# Start DokuWiki environment
+cd dokuwiki-dev
+./start.sh
+
+# Run test client
+npm run dev
+
+# Stop DokuWiki environment
+cd dokuwiki-dev
+./stop.sh
+```
+
+#### IntelliJ IDEA Integration
+
+The project includes run configurations for:
+- Running tests
+- Generating test coverage reports
+- Running the test client
+- Linting and formatting code
+- Starting and stopping the DokuWiki environment
+
+These are available in the Run Configurations dropdown.
+
+## Project Architecture
+
+### Authentication Module (`src/auth.js`)
+
+Provides authentication mechanisms for DokuWiki:
+- Basic Authentication (username/password)
+- Bearer Token Authentication
+
+```javascript
+const headers = createAuthHeaders({
+ username: 'admin',
+ password: 'password'
+});
+```
+
+### API Client (`src/apiClient.js`)
+
+Handles communication with the DokuWiki JSON-RPC API:
+- Creates and manages connections
+- Handles request/response cycle
+- Integrates with error handling
+
+```javascript
+const client = await createClient({
+ wikiUrl: 'http://localhost:8080',
+ username: 'admin',
+ password: 'password'
+});
+
+const result = await client.call('core.getPage', {
+ page: 'start'
+});
+```
+
+### Error Handler (`src/errorHandler.js`)
+
+Provides enhanced error handling with:
+- User-friendly messages
+- Recovery suggestions
+- Error categorization
+
+```javascript
+try {
+ // API call
+} catch (error) {
+ const enhancedError = handleDokuWikiError(error);
+ console.error(`Error: ${enhancedError.userMessage}`);
+ console.error(`Recovery: ${enhancedError.recoverySuggestion}`);
+}
+```
+
+## DokuWiki API Reference
+
+Common operations:
+
+### Pages
+
+- `core.getPage`: Get page content
+- `core.savePage`: Create or update page
+- `core.appendPage`: Append content to page
+- `core.listPages`: List pages in namespace
+- `core.getPageInfo`: Get page metadata
+- `core.getPageHistory`: Get page revision history
+
+### Media
+
+- `core.listMedia`: List media files
+- `core.getMedia`: Download media file
+- `core.saveMedia`: Upload media file
+- `core.deleteMedia`: Delete media file
+
+## Contributing Guidelines
+
+1. **Branch naming:**
+ - `feature/feature-name`
+ - `fix/issue-id-description`
+ - `refactor/scope`
+
+2. **Commit messages:**
+ - Format: `(): `
+ - Example: `feat(auth): Add support for token-based authentication`
+
+3. **Code quality:**
+ - Maintain test coverage (80%+)
+ - Follow ESLint rules
+ - Document public methods with JSDoc
diff --git a/README.md b/README.md
index ce64161..3855c83 100644
--- a/README.md
+++ b/README.md
@@ -7,17 +7,40 @@ A powerful plugin for managing DokuWiki websites directly from TypingMind. This
- **Page Operations**: Get, create, edit, and search wiki pages
- **Media Operations**: List, upload, download, and delete media files
- **Information Retrieval**: Get page history, metadata, and search content
-- **Secure Authentication**: Basic authentication with your DokuWiki credentials
+- **Secure Authentication**: Support for both Basic Authentication and Bearer Token authentication
## Setup
1. **Import the Plugin** into your TypingMind workspace
2. **Configure the Plugin** with your DokuWiki credentials:
- **DokuWiki URL**: The full URL to your DokuWiki instance (e.g., `https://wiki.example.com`)
- - **Username**: Your DokuWiki username
- - **Password**: Your DokuWiki password
+ - **Authentication Method**: Choose between Basic Auth (username/password) or Bearer Token
+ - **Username**: Your DokuWiki username (required for Basic Auth)
+ - **Password/Token**: Your DokuWiki password (for Basic Auth) or Bearer token (for Token Auth)
- **Default Namespace** (optional): Default namespace for operations
+## Authentication Methods
+
+The plugin supports two authentication methods:
+
+### Basic Authentication
+
+Uses username and password for authentication. This is the standard method for most DokuWiki installations.
+
+Requirements:
+- DokuWiki URL
+- Username
+- Password
+
+### Bearer Token Authentication
+
+Uses a bearer token for authentication. This is useful for applications or services that use token-based authentication.
+
+Requirements:
+- DokuWiki URL
+- Bearer Token (in the password field)
+- No username required
+
## Usage Examples
### Reading a Wiki Page
@@ -67,9 +90,97 @@ Show me all media files in the "documentation" namespace of my DokuWiki
- `save_media`: Upload a media file
- `delete_media`: Delete a media file
+## Architecture
+
+The DokuWiki Manager plugin is built with a modular architecture that provides clean separation of concerns, maintainability, and testability. The architecture consists of the following components:
+
+### Core Components
+
+- **Authentication Module** (`src/auth.js`): Handles authentication with DokuWiki API
+ - Supports both Basic Auth and Bearer Token authentication
+ - Manages secure credential handling and request signing
+ - Includes connection testing functionality
+
+- **API Client Module** (`src/apiClient.js`): Core communication layer with DokuWiki
+ - Manages JSON-RPC requests to the DokuWiki API
+ - Implements retry logic and error handling
+ - Provides utility methods for common operations
+
+- **Error Handler Module** (`src/errorHandler.js`): Comprehensive error management
+ - Maps DokuWiki error codes to user-friendly messages
+ - Provides recovery suggestions for common errors
+ - Enhances error details for better debugging
+
+- **Main Entry Point** (`implementation.js`): Plugin integration with TypingMind
+ - Routes operations to appropriate handler methods
+ - Coordinates between modules
+ - Formats responses for TypingMind compatibility
+
+### Data Flow
+
+1. **Request Initiation**: TypingMind calls the plugin with operation parameters
+2. **Authentication**: Credentials from user settings are used to authenticate
+3. **API Communication**: The request is transformed into appropriate JSON-RPC calls
+4. **Error Handling**: Responses are checked for errors and enhanced with friendly messages
+5. **Response Formatting**: Successful responses are returned to TypingMind
+
+### Error Handling Strategy
+
+The plugin implements a robust error handling strategy:
+
+1. **Error Detection**: Errors are captured at all levels (network, API, application)
+2. **Error Enhancement**: Technical errors are augmented with user-friendly messages
+3. **Recovery Suggestions**: Where possible, the plugin suggests solutions for errors
+4. **Graceful Degradation**: Some errors (like non-existent pages) return empty content instead of errors
+
+## Developer Documentation
+
+### Module Structure
+
+```
+dokuwiki-manager-plugin/
+├── implementation.js # Main entry point
+├── plugin.json # Plugin configuration
+├── src/
+│ ├── index.js # Module exports
+│ ├── auth.js # Authentication module
+│ ├── apiClient.js # API client module
+│ └── errorHandler.js # Error handling module
+├── tests/
+│ ├── auth.test.js # Authentication tests
+│ ├── apiClient.test.js # API client tests
+│ └── errorHandler.test.js # Error handler tests
+└── package.json # NPM configuration
+```
+
+### Testing
+
+The plugin has 80%+ test coverage with Jest. Each module has a corresponding test file that verifies its functionality.
+
+To run tests:
+
+```bash
+npm test # Run all tests
+npm run test:watch # Run tests in watch mode
+npm run test:coverage # Run tests with coverage report
+```
+
+### Error Code Reference
+
+The plugin maps DokuWiki error codes to user-friendly messages:
+
+- **0**: Success
+- **111**: Not allowed to read page
+- **121**: Page doesn't exist
+- **133**: Page is locked
+- **221**: Media file doesn't exist
+- **401**: Authentication failed
+- **403**: Insufficient permissions
+- **500**: Server error
+
## Limitations
-- This plugin requires valid DokuWiki user credentials with appropriate permissions
+- This plugin requires valid DokuWiki credentials with appropriate permissions
- Operations are limited to the permissions of the provided user account
- Large media files may encounter issues with browser limitations
@@ -79,9 +190,10 @@ Your DokuWiki credentials are stored in your TypingMind account and are transmit
## Troubleshooting
-- **Authentication Errors**: Verify your username and password
+- **Authentication Errors**: Verify your username and password, or check that your token is valid
- **Permission Errors**: Ensure your user has the necessary permissions in DokuWiki
- **Connection Errors**: Verify the DokuWiki URL is correct and accessible
+- **Token Issues**: Bearer tokens may expire; generate a new token if necessary
## License
diff --git a/dokuwiki-dev/config/dokuwiki-config/acl.auth.php b/dokuwiki-dev/config/dokuwiki-config/acl.auth.php
new file mode 100644
index 0000000..1dacf71
--- /dev/null
+++ b/dokuwiki-dev/config/dokuwiki-config/acl.auth.php
@@ -0,0 +1,11 @@
+# acl.auth.php
+#
+# Don't modify the lines above
+#
+# Access Control Lists
+#
+# Auto-generated by install script
+# Date: Wed, 21 May 2025 16:43:30 +0200
+* @ALL 1
+* @user 8
+* admin 16
diff --git a/dokuwiki-dev/config/dokuwiki-config/local.php b/dokuwiki-dev/config/dokuwiki-config/local.php
new file mode 100644
index 0000000..753bfff
--- /dev/null
+++ b/dokuwiki-dev/config/dokuwiki-config/local.php
@@ -0,0 +1,26 @@
+
+# Don't modify the lines above
+#
+# Userfile
+#
+# Auto-generated by install script
+# Date: Wed, 21 May 2025 16:43:30 +0200
+#
+# Format:
+# login:passwordhash:Real Name:email:groups,comma,separated
+# Password: DevTest2025!
+admin:$2y$10$8Uc1ELiFnFpGlpLSpSxqXuePxTLgITYte1NXxlETqM4ZJjcme24wG:Michael Wegener:mw@satware.com:admin,user
diff --git a/dokuwiki-dev/docker-compose.yml b/dokuwiki-dev/docker-compose.yml
new file mode 100644
index 0000000..5912550
--- /dev/null
+++ b/dokuwiki-dev/docker-compose.yml
@@ -0,0 +1,16 @@
+services:
+ dokuwiki:
+ image: lscr.io/linuxserver/dokuwiki:latest
+ container_name: dokuwiki_dev
+ environment:
+ - PUID=1000 # Update with your user ID (run 'id -u' to find)
+ - PGID=1000 # Update with your group ID (run 'id -g' to find)
+ - TZ=Europe/Berlin # Update with your timezone
+ - PHP_MEMORYLIMIT=512M
+ volumes:
+ - ./data:/config
+ # This maps the parent directory (your plugin) to the DokuWiki plugins directory
+ - ..:/config/plugins/dokuwiki-manager
+ ports:
+ - 8080:80
+ restart: unless-stopped
diff --git a/dokuwiki-dev/start.sh b/dokuwiki-dev/start.sh
new file mode 100755
index 0000000..2033f9e
--- /dev/null
+++ b/dokuwiki-dev/start.sh
@@ -0,0 +1,18 @@
+#!/bin/bash
+echo "Starting DokuWiki development environment..."
+
+# Get user and group IDs for correct permissions
+USER_ID=$(id -u)
+GROUP_ID=$(id -g)
+
+# Update Docker Compose file with current user/group IDs
+sed -i "s/PUID=1000/PUID=$USER_ID/" docker-compose.yml
+sed -i "s/PGID=1000/PGID=$GROUP_ID/" docker-compose.yml
+
+# Create data directory if it doesn't exist
+mkdir -p data
+
+docker-compose up -d
+echo "DokuWiki is starting at http://localhost:8080"
+echo "For first-time setup, go to http://localhost:8080/install.php"
+echo "To stop the environment, run: ./stop.sh"
diff --git a/dokuwiki-dev/stop.sh b/dokuwiki-dev/stop.sh
new file mode 100755
index 0000000..8ed40d2
--- /dev/null
+++ b/dokuwiki-dev/stop.sh
@@ -0,0 +1,4 @@
+#!/bin/bash
+echo "Stopping DokuWiki development environment..."
+docker-compose down
+echo "DokuWiki environment stopped."
diff --git a/examples/test-client.js b/examples/test-client.js
new file mode 100644
index 0000000..7fe7dcb
--- /dev/null
+++ b/examples/test-client.js
@@ -0,0 +1,109 @@
+/**
+ * Test client for DokuWiki Manager Plugin
+ * This example demonstrates how to use the modular API client with a local DokuWiki instance
+ */
+
+const { apiClient } = require('../src');
+const { handleDokuWikiError } = require('../src/errorHandler');
+
+// DokuWiki configuration - Update with your local instance details
+const config = {
+ wikiUrl: 'http://localhost:8080',
+ username: 'admin',
+ password: 'DevTest2025!',
+ authMethod: 'BASIC_AUTH',
+ defaultNamespace: ''
+};
+
+/**
+ * Test various DokuWiki API operations
+ */
+async function testDokuWikiAPI() {
+ let client;
+
+ try {
+ // Create API client
+ console.log('Connecting to DokuWiki instance...');
+ client = await apiClient.createApiClient(config);
+ console.log('Connected successfully!');
+
+ // Get wiki title
+ const wikiTitle = await client.call('core.getWikiTitle');
+ console.log(`Wiki Title: ${wikiTitle}`);
+
+ // Get page content
+ try {
+ console.log('\nFetching content of "start" page...');
+ const pageContent = await client.call('core.getPage', {
+ page: 'start'
+ });
+ console.log('Page content:\n-------------------');
+ console.log(pageContent || '(Empty page)');
+ console.log('-------------------');
+ } catch (error) {
+ // Handle DokuWiki-specific errors
+ const enhancedError = handleDokuWikiError(error);
+ console.error(`Error fetching page: ${enhancedError.userMessage || error.message}`);
+ if (enhancedError.recoverySuggestion) {
+ console.error(`Recovery suggestion: ${enhancedError.recoverySuggestion}`);
+ }
+ }
+
+ // List pages in wiki
+ try {
+ console.log('\nListing pages in root namespace...');
+ const pages = await client.call('core.listPages', {
+ namespace: '',
+ depth: 1
+ });
+
+ console.log('Pages:');
+ if (Array.isArray(pages)) {
+ pages.forEach((page, index) => {
+ // Handle different possible return formats
+ if (typeof page === 'string') {
+ console.log(`- ${page}`);
+ } else if (typeof page === 'object') {
+ const pageId = page.id || page.page || `Page ${index + 1}`;
+ const pageSize = page.size || 'unknown size';
+ const pageMtime = page.mtime ? new Date(page.mtime * 1000).toLocaleString() : 'unknown date';
+ console.log(`- ${pageId} (${pageSize} bytes, last modified: ${pageMtime})`);
+ } else {
+ console.log(`- ${String(page)}`);
+ }
+ });
+ } else {
+ console.log('No pages found or unexpected result format:', pages);
+ }
+ } catch (error) {
+ const enhancedError = handleDokuWikiError(error);
+ console.error(`Error listing pages: ${enhancedError.userMessage || error.message}`);
+ }
+
+ // Create a test page
+ try {
+ console.log('\nCreating a test page...');
+ await client.call('core.savePage', {
+ page: 'test_page',
+ text: '====== Test Page ======\n\nThis page was created by the DokuWiki Manager Plugin test client.',
+ summary: 'Created by test client'
+ });
+ console.log('Test page created successfully!');
+ } catch (error) {
+ const enhancedError = handleDokuWikiError(error);
+ console.error(`Error creating page: ${enhancedError.userMessage || error.message}`);
+ }
+
+ } catch (error) {
+ console.error('Error connecting to DokuWiki:');
+ console.error(error.message);
+ if (error.stack) {
+ console.error(error.stack);
+ }
+ }
+}
+
+// Run the test client
+testDokuWikiAPI().catch(error => {
+ console.error('Unhandled error in test client:', error);
+});
diff --git a/implementation.js b/implementation.js
index e390775..b64581c 100644
--- a/implementation.js
+++ b/implementation.js
@@ -6,18 +6,30 @@
* License: MIT
*/
-// Main function that will be called by TypingMind
-async function manage_dokuwiki(params, userSettings) {
- // Parameter validation
- if (!params.operation) {
- throw new Error("Operation parameter is required");
- }
-
- // Authenticate and create the JSON-RPC client
- const client = await createJsonRpcClient(userSettings);
+// Import modules
+const { apiClient } = require('./src/index');
+const { handleApiError, getRecoverySuggestion } = require('./src/errorHandler');
- // Route to the appropriate handler based on the operation
+/**
+ * Main entry point for the DokuWiki Manager plugin
+ * Called by Typing Mind when the plugin is invoked
+ *
+ * @param {Object} params - Parameters from the plugin function specification
+ * @param {Object} userSettings - User settings from the plugin configuration
+ * @returns {any} - Result of the operation
+ * @throws {Error} - If the operation fails
+ */
+async function manage_dokuwiki(params, userSettings) {
try {
+ // Validate operation parameter
+ if (!params || !params.operation) {
+ throw new Error("Operation parameter is required");
+ }
+
+ // Create and initialize the API client
+ const client = await apiClient.createApiClient(userSettings);
+
+ // Route to the appropriate handler based on the operation
switch (params.operation) {
case "get_page":
return await getPage(params, client);
@@ -26,7 +38,7 @@ async function manage_dokuwiki(params, userSettings) {
case "append_page":
return await appendPage(params, client);
case "list_pages":
- return await listPages(params, client);
+ return await listPages(params, client, userSettings);
case "search_pages":
return await searchPages(params, client);
case "get_page_info":
@@ -34,7 +46,7 @@ async function manage_dokuwiki(params, userSettings) {
case "get_page_history":
return await getPageHistory(params, client);
case "list_media":
- return await listMedia(params, client);
+ return await listMedia(params, client, userSettings);
case "get_media":
return await getMedia(params, client);
case "save_media":
@@ -45,102 +57,15 @@ async function manage_dokuwiki(params, userSettings) {
throw new Error(`Unsupported operation: ${params.operation}`);
}
} catch (error) {
- // Enhanced error handling
- if (error.code) {
- // Handle specific DokuWiki API error codes
- handleDokuWikiError(error);
+ // Enhance error with recovery suggestions
+ const enhancedError = handleApiError(error);
+ const suggestion = getRecoverySuggestion(enhancedError);
+
+ if (suggestion) {
+ enhancedError.message += `\n\nPossible solution: ${suggestion}`;
}
- throw error;
- }
-}
-
-// Helper Functions
-
-/**
- * Create a JSON-RPC client with authentication
- */
-async function createJsonRpcClient(userSettings) {
- const { wikiUrl, username, password } = userSettings;
-
- if (!wikiUrl) {
- throw new Error("DokuWiki URL is required in the plugin settings");
- }
-
- if (!username || !password) {
- throw new Error("DokuWiki username and password are required in the plugin settings");
- }
-
- // Create a basic client object with auth details
- const client = {
- baseUrl: wikiUrl.endsWith('/') ? `${wikiUrl}lib/exe/jsonrpc.php` : `${wikiUrl}/lib/exe/jsonrpc.php`,
- auth: `Basic ${btoa(`${username}:${password}`)}`,
- async call(method, params = {}) {
- const response = await fetch(this.baseUrl, {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- 'Authorization': this.auth
- },
- body: JSON.stringify({
- jsonrpc: '2.0',
- id: Date.now(),
- method: method,
- params: params
- })
- });
-
- if (!response.ok) {
- throw new Error(`HTTP error! Status: ${response.status}`);
- }
-
- const result = await response.json();
-
- if (result.error) {
- const error = new Error(result.error.message);
- error.code = result.error.code;
- throw error;
- }
-
- return result.result;
- }
- };
-
- // Test the connection
- try {
- await client.call('core.getWikiTitle');
- return client;
- } catch (error) {
- throw new Error(`Failed to connect to DokuWiki: ${error.message}`);
- }
-}
-
-/**
- * Handle DokuWiki-specific error codes
- */
-function handleDokuWikiError(error) {
- // Map of DokuWiki error codes to user-friendly messages
- const errorMap = {
- 0: "Success",
- 111: "You are not allowed to read this page",
- 121: "The requested page (revision) does not exist",
- 131: "Empty or invalid page ID given",
- 132: "Refusing to write an empty new wiki page",
- 133: "The page is currently locked",
- 134: "The page content was blocked",
- 211: "You are not allowed to read this media file",
- 212: "You are not allowed to delete this media file",
- 221: "The requested media file (revision) does not exist",
- 231: "Empty or invalid media ID given",
- 232: "Media file is still referenced",
- 233: "Failed to delete media file",
- 234: "Invalid base64 encoded data",
- 235: "Empty file given",
- 236: "Failed to save media"
- };
-
- // Enhance the error message if we have a specific message for this code
- if (errorMap[error.code]) {
- error.message = `${errorMap[error.code]} (Code: ${error.code})`;
+
+ throw enhancedError;
}
}
@@ -155,12 +80,12 @@ async function getPage(params, client) {
try {
const result = await client.call('core.getPage', {
page: params.pageId,
- rev: params.revision || 0
+ rev: params.rev || 0
});
return result;
} catch (error) {
- // Specific handling for get_page errors
+ // Special handling for non-existent pages
if (error.code === 121) {
return ""; // Return empty string for non-existent pages (matches DokuWiki behavior)
}
@@ -183,7 +108,7 @@ async function savePage(params, client) {
page: params.pageId,
text: params.content,
summary: params.summary || "",
- isminor: params.isMinor || false
+ isminor: params.minor || false
});
}
@@ -202,21 +127,22 @@ async function appendPage(params, client) {
page: params.pageId,
text: params.content,
summary: params.summary || "",
- isminor: params.isMinor || false
+ isminor: params.minor || false
});
}
/**
* List pages in a namespace
*/
-async function listPages(params, client) {
+async function listPages(params, client, userSettings) {
const namespace = params.namespace || userSettings?.defaultNamespace || "";
const depth = params.depth || 1;
+ const includeHash = params.include_hash || false;
const result = await client.call('core.listPages', {
namespace: namespace,
depth: depth,
- hash: false
+ hash: includeHash
});
return result;
@@ -226,12 +152,14 @@ async function listPages(params, client) {
* Search pages by content
*/
async function searchPages(params, client) {
- if (!params.searchQuery) {
+ if (!params.searchQuery && !params.query) {
throw new Error("Search query is required for search_pages operation");
}
+ const query = params.query || params.searchQuery;
+
const result = await client.call('core.searchPages', {
- query: params.searchQuery
+ query: query
});
return result;
@@ -247,9 +175,9 @@ async function getPageInfo(params, client) {
const result = await client.call('core.getPageInfo', {
page: params.pageId,
- rev: params.revision || 0,
- author: true,
- hash: false
+ rev: params.rev || 0,
+ author: params.include_author || false,
+ hash: params.include_hash || false
});
return result;
@@ -274,16 +202,17 @@ async function getPageHistory(params, client) {
/**
* List media files in a namespace
*/
-async function listMedia(params, client) {
+async function listMedia(params, client, userSettings) {
const namespace = params.namespace || userSettings?.defaultNamespace || "";
const depth = params.depth || 1;
const pattern = params.pattern || "";
+ const includeHash = params.include_hash || false;
const result = await client.call('core.listMedia', {
namespace: namespace,
pattern: pattern,
depth: depth,
- hash: false
+ hash: includeHash
});
return result;
@@ -299,7 +228,7 @@ async function getMedia(params, client) {
const result = await client.call('core.getMedia', {
media: params.mediaId,
- rev: params.revision || 0
+ rev: params.rev || 0
});
return result;
@@ -312,13 +241,16 @@ async function saveMedia(params, client) {
if (!params.mediaId) {
throw new Error("Media ID is required for save_media operation");
}
- if (!params.content) {
+
+ // Check for content parameter (support both content and base64_content)
+ const base64Content = params.base64_content || params.content;
+ if (!base64Content) {
throw new Error("Content (base64 encoded) is required for save_media operation");
}
const result = await client.call('core.saveMedia', {
media: params.mediaId,
- base64: params.content,
+ base64: base64Content,
overwrite: params.overwrite || false
});
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..07a5185
--- /dev/null
+++ b/package.json
@@ -0,0 +1,55 @@
+{
+ "name": "dokuwiki-manager-plugin",
+ "version": "1.0.0",
+ "description": "A TypingMind plugin for managing DokuWiki websites",
+ "main": "implementation.js",
+ "scripts": {
+ "test": "jest",
+ "test:watch": "jest --watch",
+ "test:coverage": "jest --coverage",
+ "lint": "eslint --ext .js .",
+ "format": "prettier --write \"**/*.{js,json,md}\"",
+ "dev": "node examples/test-client.js"
+ },
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/satwareAG/dokuwiki-manager-plugin.git"
+ },
+ "keywords": [
+ "dokuwiki",
+ "typingmind",
+ "plugin",
+ "wiki",
+ "json-rpc"
+ ],
+ "author": "satware AG",
+ "license": "MIT",
+ "bugs": {
+ "url": "https://github.com/satwareAG/dokuwiki-manager-plugin/issues"
+ },
+ "homepage": "https://github.com/satwareAG/dokuwiki-manager-plugin#readme",
+ "devDependencies": {
+ "eslint": "^9.27.0",
+ "eslint-config-prettier": "^10.1.5",
+ "eslint-plugin-jest": "^28.11.0",
+ "jest": "^29.7.0",
+ "jest-environment-jsdom": "^29.7.0",
+ "node-fetch": "^2.7.0",
+ "prettier": "^3.5.3"
+ },
+ "jest": {
+ "testEnvironment": "jsdom",
+ "coverageThreshold": {
+ "global": {
+ "branches": 80,
+ "functions": 80,
+ "lines": 80,
+ "statements": 80
+ }
+ },
+ "collectCoverageFrom": [
+ "src/**/*.js",
+ "implementation.js"
+ ]
+ }
+}
diff --git a/plugin.json b/plugin.json
index 1fb48ce..c74af37 100644
--- a/plugin.json
+++ b/plugin.json
@@ -2,9 +2,91 @@
"version": 1,
"uuid": "dokuwiki-manager-typingmind-plugin",
"iconURL": "https://www.dokuwiki.org/lib/tpl/dokuwiki/images/logo.png",
- "emoji": "📝",
+ "emoji": "📚",
"title": "DokuWiki Manager",
- "userSettings": "[{\"name\":\"wikiUrl\",\"label\":\"DokuWiki URL\",\"type\":\"text\",\"required\":true,\"description\":\"The full URL to your DokuWiki instance (e.g., https://wiki.example.com)\"},{\"name\":\"username\",\"label\":\"Username\",\"type\":\"text\",\"required\":true,\"description\":\"Your DokuWiki username\"},{\"name\":\"password\",\"label\":\"Password\",\"type\":\"password\",\"required\":true,\"description\":\"Your DokuWiki password\"},{\"name\":\"defaultNamespace\",\"label\":\"Default Namespace\",\"type\":\"text\",\"description\":\"Optional default namespace for operations\"}]",
- "openaiSpec": "{\"name\":\"manage_dokuwiki\",\"parameters\":{\"type\":\"object\",\"required\":[\"operation\"],\"properties\":{\"operation\":{\"type\":\"string\",\"description\":\"The operation to perform on the DokuWiki instance\",\"enum\":[\"get_page\",\"save_page\",\"append_page\",\"list_pages\",\"search_pages\",\"get_page_info\",\"get_page_history\",\"list_media\",\"get_media\",\"save_media\",\"delete_media\"]},\"pageId\":{\"type\":\"string\",\"description\":\"The ID of the wiki page to operate on\"},\"mediaId\":{\"type\":\"string\",\"description\":\"The ID of the media file to operate on\"},\"content\":{\"type\":\"string\",\"description\":\"The content to write to the page or file\"},\"namespace\":{\"type\":\"string\",\"description\":\"The namespace to list pages or media from\"},\"searchQuery\":{\"type\":\"string\",\"description\":\"The search query for finding pages\"}}},\"description\":\"Manage DokuWiki websites through the JSON-RPC API. Perform operations on wiki pages and media files.\"}",
+ "userSettings": [
+ {
+ "name": "wikiUrl",
+ "label": "DokuWiki URL",
+ "type": "text",
+ "required": true,
+ "description": "The full URL to your DokuWiki instance (e.g., https://wiki.example.com)"
+ },
+ {
+ "name": "authMethod",
+ "label": "Authentication Method",
+ "type": "enum",
+ "required": true,
+ "values": ["BASIC_AUTH", "BEARER_TOKEN"],
+ "description": "Choose authentication method (Basic Auth for username/password or Bearer Token for token-based auth)"
+ },
+ {
+ "name": "username",
+ "label": "Username",
+ "type": "text",
+ "required": false,
+ "description": "Your DokuWiki username (only required for Basic Auth)"
+ },
+ {
+ "name": "password",
+ "label": "Password/Token",
+ "type": "password",
+ "required": true,
+ "description": "Your DokuWiki password (for Basic Auth) or Bearer token (for Token Auth)"
+ },
+ {
+ "name": "defaultNamespace",
+ "label": "Default Namespace",
+ "type": "text",
+ "description": "Optional default namespace for operations"
+ }
+ ],
+ "openaiSpec": {
+ "name": "manage_dokuwiki",
+ "parameters": {
+ "type": "object",
+ "required": ["operation"],
+ "properties": {
+ "operation": {
+ "type": "string",
+ "description": "The operation to perform on the DokuWiki instance",
+ "enum": [
+ "get_page",
+ "save_page",
+ "append_page",
+ "list_pages",
+ "search_pages",
+ "get_page_info",
+ "get_page_history",
+ "list_media",
+ "get_media",
+ "save_media",
+ "delete_media"
+ ]
+ },
+ "pageId": {
+ "type": "string",
+ "description": "The ID of the wiki page to operate on"
+ },
+ "mediaId": {
+ "type": "string",
+ "description": "The ID of the media file to operate on"
+ },
+ "content": {
+ "type": "string",
+ "description": "The content to write to the page or file"
+ },
+ "namespace": {
+ "type": "string",
+ "description": "The namespace to list pages or media from"
+ },
+ "searchQuery": {
+ "type": "string",
+ "description": "The search query for finding pages"
+ }
+ }
+ },
+ "description": "Manage DokuWiki websites through the JSON-RPC API. Perform operations on wiki pages and media files."
+ },
"implementationType": "javascript"
}
\ No newline at end of file
diff --git a/src/apiClient.js b/src/apiClient.js
new file mode 100644
index 0000000..15b4d32
--- /dev/null
+++ b/src/apiClient.js
@@ -0,0 +1,136 @@
+/**
+ * DokuWiki API client module
+ * Provides a client for making JSON-RPC calls to DokuWiki API.
+ *
+ * @module dokuwiki-manager/apiClient
+ * @author satware AG
+ * @license MIT
+ */
+
+// Import modules
+const auth = require('./auth');
+const { handleApiError, getRecoverySuggestion } = require('./errorHandler');
+
+/**
+ * Create and initialize a DokuWiki API client
+ *
+ * @param {Object} userSettings - User settings from Typing Mind plugin
+ * @param {boolean} testConnection - Whether to test the connection (default: true)
+ * @returns {Promise