Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,6 @@ composer.lock

Tests/app/tmp/
.phpunit.result.cache

.docs
.demo
4 changes: 2 additions & 2 deletions EventDispatcher/CleanTranslationCacheListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,8 @@ private function isCacheExpired()
$cache_file = strtr($cache_file, '/', '\\');
$cache_dir = strtr($cache_dir, '/', '\\');
}
if (!\is_dir($cache_dir)) {
\mkdir($cache_dir);
if (!\is_dir($cache_dir) && !\mkdir($cache_dir, 0777, true) && !\is_dir($cache_dir)) {
throw new \RuntimeException(sprintf('Directory "%s" was not created', $cache_dir));
}
if (!\file_exists($cache_file)) {
\touch($cache_file);
Expand Down
126 changes: 123 additions & 3 deletions Resources/public/js/translation.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,15 @@ const TranslationManager = (() => {

const debounceTimeouts = {};

document.addEventListener('DOMContentLoaded', function () {
reloadGrid();
const initializeGrid = () => {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there no better DomReady event?

// Small delay to ensure DOM is fully ready
setTimeout(() => {
// Read filter parameters from URL (query string or hash) and apply them
parseUrlFilters().then((hasFilters) => {
// Reload grid with filters applied
reloadGrid();
});
}, 100);

document.querySelectorAll('.input-sm').forEach(input => {
input.addEventListener('keyup', function() {
Expand All @@ -26,11 +33,22 @@ const TranslationManager = (() => {
_currentPage = 1;
_order = 'id';
_direction = 'asc';
// Update URL with current filter values
updateUrlWithFilters();
// Reload grid
reloadGrid();
}, 200);
});
});
});
};

// Check if DOM is already loaded
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initializeGrid);
} else {
// DOM is already loaded, execute immediately
initializeGrid();
}
};

const sharedMessage = {
Expand Down Expand Up @@ -393,6 +411,108 @@ const TranslationManager = (() => {

const getMaxPageNumber = (total) => Math.ceil(total / translationCfg.maxPageNumber);

const parseUrlFilters = () =>
{
return new Promise((resolve) => {
let hasFilters = false;
const filterPromises = [];

// First, try to parse from query string (?filter[_domain]=validators)
const queryString = window.location.search;
let params = null;

if (queryString) {
params = new URLSearchParams(queryString.substring(1)); // Remove '?'
} else {
// Fallback to hash fragment (#!?filter[_domain]=validators)
const hash = window.location.hash;
if (hash && hash.startsWith('#!?')) {
params = new URLSearchParams(hash.substring(3)); // Remove '#!?'
}
}

if (!params) {
resolve(false);
return;
}

// Process filter parameters
params.forEach((value, key) => {
if (key.startsWith('filter[') && key.endsWith(']')) {
// Extract column name from filter[column]
const column = key.substring(7, key.length - 1);
// Map column names to input IDs (e.g., _domain -> __domain)
// Note: locales keep their original name (en, es, etc.)
let inputId = column;
if (column === '_domain') {
inputId = '__domain';
}
// _key and locales use their column name as input ID

// Wait for input to be available
const filterPromise = new Promise((filterResolve) => {
const trySetFilter = (attempts = 0) => {
const input = document.getElementById(inputId);
if (input) {
input.value = decodeURIComponent(value);
hasFilters = true;
filterResolve();
} else if (attempts < 10) {
// Retry up to 10 times with 50ms delay
setTimeout(() => trySetFilter(attempts + 1), 50);
} else {
// Give up after 10 attempts
filterResolve();
}
};
trySetFilter();
});

filterPromises.push(filterPromise);
}
});

// Wait for all filters to be applied
if (filterPromises.length > 0) {
Promise.all(filterPromises).then(() => {
resolve(hasFilters);
});
} else {
resolve(false);
}
});
};

const updateUrlWithFilters = () =>
{
const params = new URLSearchParams();
let hasFilters = false;

document.querySelectorAll('.table input').forEach(input => {
const column = input.getAttribute('id');
const filterValue = input.value.trim();
if (filterValue !== '') {
// Map input IDs back to column names for URL
// Note: locales (en, es, etc.) and _key keep their input ID as column name
let urlColumn = column;
if (column === '__domain') {
urlColumn = '_domain';
}
// For other columns (locales, _key), use the input ID directly
params.set(`filter[${urlColumn}]`, filterValue);
hasFilters = true;
}
});

// Update URL without reloading the page
if (window.history && window.history.replaceState) {
const newUrl = hasFilters
? window.location.pathname + '?' + params.toString()
: window.location.pathname;
window.history.replaceState(null, '', newUrl);
}
};

const addFilteredValuesToParams = (params) =>
{
let search = false;
Expand Down
4 changes: 2 additions & 2 deletions Resources/views/Translation/overview.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
<h1>
{{ 'overview.page_title'|trans }}
<div class="pull-right">
<a href="{{ path('lexik_translation_grid') }}" role="button" class="btn btn-primary">
<a href="{{ path('lexik_translation_grid', {'_locale': app.request.attributes.get('_locale')|default(app.request.locale|default('en'))}) }}" role="button" class="btn btn-primary">
<span class="glyphicon glyphicon-th"></span>
{{ 'overview.show_grid'|trans }}
</a>
Expand Down Expand Up @@ -49,7 +49,7 @@
<tbody>
{% for domain in domains %}
<tr columns="columns">
<td><a href="{{ path('lexik_translation_grid') }}#!?filter[_domain]={{ domain | url_encode }}">{{ domain }}</a></td>
<td><a href="{{ path('lexik_translation_grid', {'_locale': app.request.attributes.get('_locale')|default(app.request.locale|default('en'))}) }}?filter[_domain]={{ domain | url_encode }}">{{ domain }}</a></td>
{% for locale in locales %}
<td class="text-center">
<span class="text {{ stats[domain][locale]['completed'] == 100 ? 'text-success' : 'text-danger' }}">
Expand Down
2 changes: 0 additions & 2 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
version: '3.8'

networks:
lexik_translation_network:
driver: bridge
Expand Down