Customization Overview
Visual Diff Merge offers several ways to customize its behavior and appearance:
- Configuration Settings - Adjust behavior through
api/config.php
- Styling Customization - Modify CSS to change the appearance
- Language and Translations - Customize text strings and add languages
- Extending Functionality - Add custom components or modify existing ones
- Integration with Other Systems - Use Visual Diff Merge in your own applications
Configuration Customization
The api/config.php
file is the primary way to customize Visual Diff Merge's behavior.
It controls both PHP (server-side) and JavaScript (client-side) settings.
For detailed information about the structure of the configuration file and how to set it up, refer to the Configuration section in the Installation guide.
Example Configuration
<?php
return [
'php' => [
'debug' => [
'enabled' => true, // Enable debugging
'logLevel' => 3, // Verbose logging
'logFile' => 'custom-debug.log' // Custom log file
],
'diff' => [
'contextLines' => 5, // Show 5 lines of context
'ignoreWhitespace' => true, // Ignore whitespace changes
'ignoreCase' => false
],
'security' => [
'csrfProtection' => true,
'salt' => 'your-custom-salt-string', // Custom security salt
'allowedDirectories' => [
realpath(__DIR__ . '/../diff-viewer/samples'),
'/var/www/html/my-project'
]
]
],
'javascript' => [
'lang' => 'fr', // Set French as default language
'debug' => true,
'logLevel' => 3,
'theme' => [
'defaultFamily' => 'github', // Use GitHub theme family
'defaultMode' => 'light', // Default to light mode
'showSelector' => true
],
// Custom save options
'saveOptions' => [
'saveToOriginal' => true,
'saveWithSuffix' => true,
'saveToOld' => false, // Disable saving to old file
'saveToOldWithSuffix' => false,
'saveToBoth' => true,
'saveToBothWithSuffix' => true
]
]
];
Key Customization Options
Diff Behavior
-
php.diff.contextLines
- Adjust the number of unchanged lines shown around changes. Lower values show less context, higher values show more context. -
php.diff.ignoreWhitespace
- Set totrue
to ignore whitespace changes when comparing content. Useful for code comparisons where formatting may differ. -
php.diff.ignoreCase
- Set totrue
to ignore case differences when comparing content.
Theme and UI
-
javascript.theme.defaultFamily
- Change the default syntax highlighting theme family. Options include:'atom-one'
- Atom One theme (default)'github'
- GitHub theme'vs'
- Visual Studio theme'xcode'
- Xcode theme- And many others from highlight.js
-
javascript.theme.defaultMode
- Set default theme mode:'dark'
- Dark mode (default)'light'
- Light mode
-
javascript.theme.showSelector
- Control visibility of the theme selector. Set tofalse
to hide it.
Save Options
Control which save options are available in the merge UI:
javascript.saveOptions.saveToOriginal
- Save to current file (new file)javascript.saveOptions.saveWithSuffix
- Save to current file with suffixjavascript.saveOptions.saveToOld
- Save to old file (overwrite)javascript.saveOptions.saveToOldWithSuffix
- Save to old file with suffixjavascript.saveOptions.saveToBoth
- Save to both files (overwrite both)javascript.saveOptions.saveToBothWithSuffix
- Save to both files with suffix
Styling Customization
Visual Diff Merge's appearance can be customized through CSS. There are several approaches:
Approach 1: Override with Custom CSS
Add your own CSS file after loading the Visual Diff Merge CSS:
<!-- Load Visual Diff Merge CSS first -->
<link rel="stylesheet" href="path/to/diff-viewer.min.css">
<link rel="stylesheet" href="path/to/diff-viewer-theme.min.css">
<!-- Then load your custom CSS to override styles -->
<link rel="stylesheet" href="path/to/your-custom-styles.css">
Theme Customization
The diff-viewer-theme.min.css
file contains styles for the diff viewer UI elements (buttons, alerts, etc.)
that can be customized in two main ways:
- Compile your own custom theme by modifying
src/scss/theme.scss
- Override the CSS classnames using the PHP configuration options to match your preferred CSS framework
Approach 2: Modify SCSS Source Files
If you're using the development setup, you can modify the SCSS source files directly:
- Modify files in
src/scss/
orscss-shared/
directories - Rebuild the CSS using the provided scripts:
npm run compile-scss
Important CSS Classes
Here are some key CSS classes you may want to customize:
CSS Class | Description |
---|---|
.vdm-diff__viewer |
Main diff viewer container |
.vdm-diff__chunk |
Individual diff chunk |
.vdm-diff__line--added |
Added lines |
.vdm-diff__line--removed |
Removed lines |
.vdm-diff__line--unchanged |
Unchanged lines |
.vdm-diff__panel--left |
Left panel (old content) |
.vdm-diff__panel--right |
Right panel (new content) |
.vdm-btn |
Buttons |
.vdm-loader |
Loading indicator container |
.vdm-loader__spinner |
Loading spinner animation |
.vdm-loader__message |
Loading message text |
.vdm-d-none |
Utility class to hide elements (display: none) |
.vdm-diff__navigation |
Navigation controls |
Example Custom CSS
/* Custom colors for diff chunks */
.vdm-diff__line--added {
background-color: rgba(0, 180, 0, 0.2);
}
.vdm-diff__line--removed {
background-color: rgba(255, 0, 0, 0.2);
}
/* Custom font for code */
.vdm-diff__code {
font-family: 'Fira Code', monospace;
font-size: 14px;
}
/* Custom button styling */
.vdm-btn {
border-radius: 4px;
text-transform: uppercase;
font-weight: bold;
}
/* Custom navigation controls */
.vdm-diff__navigation {
background-color: #f5f5f5;
border-bottom: 1px solid #ddd;
}
/* Highlight current chunk */
.vdm-diff__chunk.vdm-active {
box-shadow: 0 0 0 2px #4285f4;
}
Language and Translations
Visual Diff Merge supports multiple languages through translation objects in the configuration.
Setting Default Language
Set the default language in api/config.php
:
'javascript' => [
'lang' => 'fr', // Set French as default language
// Other settings...
]
Adding Custom Translations
To add or modify translations, edit the translations
section in api/config.php
:
'javascript' => [
// Other settings...
'translations' => [
'en' => [
'applyMerge' => 'Apply Merge',
'continueResolving' => 'Continue Resolving',
// Other English translations...
],
'fr' => [
'applyMerge' => 'Appliquer la fusion',
'continueResolving' => 'Continuer la résolution',
// Other French translations...
],
'es' => [
'applyMerge' => 'Aplicar fusión',
'continueResolving' => 'Continuar resolviendo',
// Other Spanish translations...
]
]
]
Adding a New Language
To add support for a new language:
- Create a new language key in the
translations
object - Provide translations for all strings
- Set the
javascript.lang
value to your new language code
'javascript' => [
'lang' => 'de', // Set German as default language
'translations' => [
// Existing translations...
'de' => [
'applyMerge' => 'Zusammenführung anwenden',
'continueResolving' => 'Weiter auflösen',
'filesIdenticalMessage' => '<strong>Dateien sind identisch</strong><br>Die verglichenen Dateien sind identisch. Keine Unterschiede gefunden.',
// Add all other required translations
]
]
]
Required Translation Keys
The full list of translatable strings is available in api/config.example.php
.
At minimum, you should provide translations for:
- UI elements (
applyMerge
,continueResolving
, etc.) - Error messages (
invalidRequestMethod
,fileNotFound
, etc.) - Loading states (
loadingContent
,processingChunks
, etc.) - Confirmation dialogs (
unresolvedConflicts
,resolveConflictsMessage
, etc.)
Extending Functionality
Visual Diff Merge can be extended in several ways to add custom functionality.
Creating Custom UI Components
Add custom UI elements to the diff viewer:
// Create a custom component
class CustomControl {
constructor(options = {}) {
this.container = options.container || document.body;
this.diffViewer = options.diffViewer;
}
initialize() {
// Create your custom UI element
const customElement = document.createElement('div');
customElement.classList.add('vdm-custom-control');
customElement.innerHTML = `
<button class="vdm-btn vdm-btn--primary">Custom Action</button>
`;
// Add event listener
const button = customElement.querySelector('button');
button.addEventListener('click', () => {
// Do something with the diff viewer
console.log('Custom action clicked');
this.diffViewer.refreshView();
});
// Append to container
this.container.appendChild(customElement);
return true;
}
}
// After initializing the diff viewer
document.addEventListener('DOMContentLoaded', function() {
// Wait for diffViewer to be initialized
const checkDiffViewer = setInterval(() => {
if (window.diffViewer) {
clearInterval(checkDiffViewer);
// Create and initialize your custom component
const customControl = new CustomControl({
container: document.querySelector('.vdm-diff__controls'),
diffViewer: window.diffViewer
});
customControl.initialize();
}
}, 100);
});
Extending Manager Classes
Extend the manager classes to add custom behavior:
// Extend the TextCompareManager class
class CustomTextCompareManager extends TextCompareManager {
constructor(options = {}) {
// Call parent constructor
super(options);
// Add custom properties
this.customProperty = options.customProperty || 'default';
}
// Override a method
async handleFormSubmit(event) {
// Call the parent method first
await super.handleFormSubmit(event);
// Add custom behavior after form submission
console.log('Form submitted with custom behavior');
this.performCustomAction();
}
// Add a new method
performCustomAction() {
console.log('Performing custom action');
// Your custom logic here
}
}
// Replace the default manager with your custom one
document.addEventListener('DOMContentLoaded', function() {
// Initialize with your custom manager
window.textCompareManager = new CustomTextCompareManager({
customProperty: 'custom value'
});
});
Adding Post-Processing Hooks
Add custom post-processing to the diff result:
// Create a post-processing hook
function addCustomPostProcessing() {
// Wait for diffViewer to be initialized
const checkDiffViewer = setInterval(() => {
if (window.diffViewer) {
clearInterval(checkDiffViewer);
// Store the original method
const originalRenderDiff = window.diffViewer.renderDiff;
// Override the method
window.diffViewer.renderDiff = function() {
// Call the original method
const result = originalRenderDiff.apply(this, arguments);
// Perform post-processing
console.log('Performing post-processing on diff');
// Example: Add line numbers to the gutter
const gutters = document.querySelectorAll('.vdm-diff__gutter');
gutters.forEach(gutter => {
const lineNumbers = gutter.querySelectorAll('.vdm-diff__line-number');
lineNumbers.forEach((lineNumber, index) => {
lineNumber.setAttribute('data-custom-index', index);
});
});
return result;
};
}
}, 100);
}
// Add the hook
document.addEventListener('DOMContentLoaded', addCustomPostProcessing);
Integration with Other Systems
Visual Diff Merge can be integrated with other systems and applications.
Embedding in Your Application
To embed Visual Diff Merge in your application:
<!-- Include required CSS files -->
<link rel="stylesheet" href="path/to/diff-viewer.min.css">
<link rel="stylesheet" href="path/to/diff-viewer-theme.min.css">
<!-- Create container for the diff viewer -->
<div id="my-diff-container"></div>
<!-- Include required JavaScript files -->
<script src="path/to/diff-viewer.min.js"></script>
<script>
// Initialize the diff viewer with your content
document.addEventListener('DOMContentLoaded', function() {
// Configuration object
const config = {
container: 'my-diff-container',
debug: false,
logLevel: 1,
old: {
type: 'text',
content: 'Old content here',
filename: 'old.txt'
},
new: {
type: 'text',
content: 'New content here',
filename: 'new.txt'
}
};
// Process diff on the server
fetch('path/to/api/diff-processor.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(config)
})
.then(response => response.json())
.then(result => {
if (result.success) {
// Merge the result config with our initial config
const mergedConfig = {...config, ...result.config};
// Initialize diff viewer
window.diffViewer = new DiffViewer(mergedConfig);
window.diffViewer.initialize();
} else {
console.error('Error processing diff:', result.error);
}
});
});
</script>
Creating Custom API Endpoints
Create custom API endpoints to integrate with your system:
<?php
// custom-integration.php
require_once __DIR__ . '/../vendor/autoload.php';
use VisualDiffMerge\Config;
use VisualDiffMerge\DiffViewer;
use VisualDiffMerge\PathManager;
// Initialize config
Config::init();
// Set JSON response headers
header('Content-Type: application/json');
// Get data from your system
$oldVersion = yourSystem->getVersion('old');
$newVersion = yourSystem->getVersion('current');
// Create a DiffViewer instance
$diffViewer = new DiffViewer([
'debug' => Config::isDebugEnabled(),
'diffContextLines' => Config::get('php.diff.contextLines'),
'ignoreWhitespace' => Config::get('php.diff.ignoreWhitespace'),
'ignoreCase' => Config::get('php.diff.ignoreCase')
]);
try {
// Process the diff
$diffData = $diffViewer->compareContent(
$oldVersion->content,
$newVersion->content,
$newVersion->fileExtension
);
// Create secure reference IDs
$pathManager = PathManager::getInstance();
$oldRefId = $pathManager->registerPath($oldVersion->filePath);
$newRefId = $pathManager->registerPath($newVersion->filePath);
// Prepare the response
$response = [
'success' => true,
'config' => [
'diffData' => $diffData,
'serverSaveEnabled' => true,
'fileRefId' => $newRefId,
'oldFileRefId' => $oldRefId,
'newFileName' => basename($newVersion->filePath),
'oldFileName' => basename($oldVersion->filePath),
'translations' => Config::getTranslations(Config::get('javascript.lang'))
]
];
echo json_encode($response);
} catch (Exception $e) {
echo json_encode([
'success' => false,
'error' => $e->getMessage()
]);
}
Callbacks and Events
Integrate with your system using callbacks:
// Set up event listeners after DiffViewer is initialized
document.addEventListener('DOMContentLoaded', function() {
// Wait for diffViewer to be initialized
const checkDiffViewer = setInterval(() => {
if (window.diffViewer) {
clearInterval(checkDiffViewer);
// Listen for chunk selection
document.addEventListener('vdm:chunkSelected', function(event) {
console.log('Chunk selected:', event.detail);
// Integrate with your system
yourSystem.notifyChunkSelected(event.detail.chunkId, event.detail.side);
});
// Listen for merge completion
document.addEventListener('vdm:mergeCompleted', function(event) {
console.log('Merge completed:', event.detail);
// Integrate with your system
yourSystem.notifyMergeCompleted(event.detail.files);
// Navigate to a different page
setTimeout(() => {
window.location.href = '/your-system/view?file=' + event.detail.files[0].filename;
}, 2000);
});
}
}, 100);
});
Creating Plugins
Create plugins to extend Visual Diff Merge with reusable functionality:
Plugin Architecture
Follow this structure for Visual Diff Merge plugins:
// Visual Diff Merge Plugin Template
(function(window) {
'use strict';
// Plugin constructor
function VDMPlugin(options = {}) {
this.name = 'MyCustomPlugin';
this.version = '1.0.0';
this.options = options;
this.diffViewer = null;
}
// Plugin prototype
VDMPlugin.prototype = {
/**
* Initialize the plugin
* @param {DiffViewer} diffViewer - The DiffViewer instance
* @returns {boolean} Success status
*/
initialize: function(diffViewer) {
if (!diffViewer) {
console.error('DiffViewer instance is required');
return false;
}
this.diffViewer = diffViewer;
console.log(`${this.name} v${this.version} initialized`);
// Set up your plugin functionality
this.setupUI();
this.attachEventListeners();
return true;
},
/**
* Set up plugin UI
*/
setupUI: function() {
// Create and insert your UI elements
const pluginContainer = document.createElement('div');
pluginContainer.classList.add('vdm-plugin-container');
pluginContainer.innerHTML = `
<div class="vdm-plugin-header">${this.name}</div>
<div class="vdm-plugin-content">
<button class="vdm-btn vdm-plugin-btn">Plugin Action</button>
</div>
`;
// Insert into the diff viewer UI
const targetContainer = document.querySelector('.vdm-diff__controls');
if (targetContainer) {
targetContainer.appendChild(pluginContainer);
}
},
/**
* Attach event listeners
*/
attachEventListeners: function() {
// Find your plugin button
const button = document.querySelector('.vdm-plugin-btn');
if (button) {
button.addEventListener('click', this.handleAction.bind(this));
}
},
/**
* Handle plugin action
* @param {Event} event - Click event
*/
handleAction: function(event) {
console.log('Plugin action triggered');
// Example: Highlight all modified chunks
const modifiedChunks = this.diffViewer.getDiffData().chunks.filter(
chunk => chunk.type === 'modified'
);
modifiedChunks.forEach(chunk => {
// Do something with the chunk
console.log('Modified chunk:', chunk);
});
// Custom logic here
}
};
// Register plugin with Visual Diff Merge
window.registerVDMPlugin = function(diffViewer, options) {
const plugin = new VDMPlugin(options);
plugin.initialize(diffViewer);
return plugin;
};
})(window);
// Usage:
// After DiffViewer is initialized
document.addEventListener('DOMContentLoaded', function() {
// Wait for diffViewer to be initialized
const checkDiffViewer = setInterval(() => {
if (window.diffViewer) {
clearInterval(checkDiffViewer);
// Register the plugin
const plugin = window.registerVDMPlugin(window.diffViewer, {
// Plugin options
option1: 'value1',
option2: 'value2'
});
}
}, 100);
});