change template panel left, create dashboard
This commit is contained in:
parent
381f378db4
commit
539b4c094f
24 changed files with 1367 additions and 166 deletions
|
@ -1,4 +1,63 @@
|
|||
/* Layout général */
|
||||
body {
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
/* Sidebar styles */
|
||||
.sidebar {
|
||||
width: 250px;
|
||||
min-height: 100vh;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 100;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.sidebar-header {
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.sidebar .nav-link {
|
||||
padding: 0.75rem 1rem;
|
||||
border-radius: 0.25rem;
|
||||
margin-bottom: 0.25rem;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.sidebar .nav-link:hover {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.sidebar .nav-link.active {
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.sidebar .nav-link i {
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
.content-wrapper {
|
||||
margin-left: 250px;
|
||||
width: calc(100% - 250px);
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
/* For mobile devices */
|
||||
@media (max-width: 768px) {
|
||||
.sidebar {
|
||||
width: 100%;
|
||||
position: relative;
|
||||
min-height: auto;
|
||||
}
|
||||
|
||||
.content-wrapper {
|
||||
margin-left: 0;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.body-landing {
|
||||
background-color: rgb(255, 255, 255);
|
||||
min-height: 100vh;
|
||||
|
@ -9,7 +68,7 @@
|
|||
.main-header {
|
||||
background-color: #fff;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, .1);
|
||||
padding: 1rem 0;
|
||||
/*padding: 1rem 0;*/
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
|
|
|
@ -208,6 +208,13 @@ class WikiController extends AbstractController
|
|||
|
||||
// Process team members statistics
|
||||
$teamMembers = $this->processTeamMembersStats($recentChanges);
|
||||
|
||||
// Add avatar URLs to recent changes
|
||||
foreach ($recentChanges as &$change) {
|
||||
if (isset($change['user'])) {
|
||||
$change['avatar_url'] = $this->fetchUserAvatar($change['user']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the data is older than 1 hour
|
||||
|
@ -241,6 +248,72 @@ class WikiController extends AbstractController
|
|||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch and cache user avatar from OSM Wiki
|
||||
*
|
||||
* @param string $username OSM Wiki username
|
||||
* @return string|null URL of the avatar or null if not found
|
||||
*/
|
||||
private function fetchUserAvatar(string $username): ?string
|
||||
{
|
||||
// Create cache directory if it doesn't exist
|
||||
$cacheDir = $this->getParameter('kernel.project_dir') . '/wiki_compare/avatar_cache';
|
||||
if (!file_exists($cacheDir)) {
|
||||
mkdir($cacheDir, 0755, true);
|
||||
}
|
||||
|
||||
// Generate cache filename
|
||||
$cacheFile = $cacheDir . '/' . md5($username) . '.json';
|
||||
|
||||
// Check if avatar is cached and not expired (7 days)
|
||||
if (file_exists($cacheFile)) {
|
||||
$cacheData = json_decode(file_get_contents($cacheFile), true);
|
||||
$cacheTime = new \DateTime($cacheData['timestamp'] ?? '2000-01-01');
|
||||
$now = new \DateTime();
|
||||
$diff = $now->diff($cacheTime);
|
||||
|
||||
// If cache is less than 7 days old, return cached avatar
|
||||
if ($diff->days < 7 && isset($cacheData['avatar_url'])) {
|
||||
return $cacheData['avatar_url'];
|
||||
}
|
||||
}
|
||||
|
||||
// Avatar not cached or cache expired, fetch from wiki
|
||||
$userPageUrl = "https://wiki.openstreetmap.org/wiki/User:" . urlencode($username);
|
||||
try {
|
||||
$response = file_get_contents($userPageUrl);
|
||||
if ($response) {
|
||||
// Parse HTML to find avatar
|
||||
$avatarUrl = null;
|
||||
|
||||
// Look for user avatar in the page
|
||||
if (preg_match('/<img[^>]*class="[^"]*userImage[^"]*"[^>]*src="([^"]+)"/', $response, $matches)) {
|
||||
$avatarUrl = $matches[1];
|
||||
|
||||
// Make URL absolute if it's relative
|
||||
if (strpos($avatarUrl, 'http') !== 0) {
|
||||
$avatarUrl = 'https://wiki.openstreetmap.org' . $avatarUrl;
|
||||
}
|
||||
}
|
||||
|
||||
// Cache the result
|
||||
$cacheData = [
|
||||
'username' => $username,
|
||||
'avatar_url' => $avatarUrl,
|
||||
'timestamp' => (new \DateTime())->format('c')
|
||||
];
|
||||
file_put_contents($cacheFile, json_encode($cacheData));
|
||||
|
||||
return $avatarUrl;
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
// Log error but continue
|
||||
error_log("Error fetching avatar for user $username: " . $e->getMessage());
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process team members statistics from recent changes data
|
||||
*
|
||||
|
@ -258,13 +331,17 @@ class WikiController extends AbstractController
|
|||
|
||||
// Initialize user data if not exists
|
||||
if (!isset($teamMembers[$user])) {
|
||||
// Fetch user avatar
|
||||
$avatarUrl = $this->fetchUserAvatar($user);
|
||||
|
||||
$teamMembers[$user] = [
|
||||
'username' => $user,
|
||||
'contributions' => 0,
|
||||
'chars_added' => 0,
|
||||
'chars_changed' => 0,
|
||||
'chars_deleted' => 0,
|
||||
'user_url' => "https://wiki.openstreetmap.org/wiki/User:" . urlencode($user)
|
||||
'user_url' => "https://wiki.openstreetmap.org/wiki/User:" . urlencode($user),
|
||||
'avatar_url' => $avatarUrl
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -628,9 +705,18 @@ class WikiController extends AbstractController
|
|||
|
||||
// Also load the word-diff based suspicious pages for comparison
|
||||
if (file_exists($wordDiffFile)) {
|
||||
$jsonData = json_decode(file_get_contents($wordDiffFile), true);
|
||||
// Use memory-efficient approach to extract only the necessary data
|
||||
$maxItems = 50; // Limit the number of items to prevent memory exhaustion
|
||||
|
||||
foreach ($jsonData as $page) {
|
||||
// Extract regular_pages and specific_pages arrays
|
||||
$regularPages = $this->extractJsonArrayByKey($wordDiffFile, 'regular_pages', $maxItems);
|
||||
$specificPages = $this->extractJsonArrayByKey($wordDiffFile, 'specific_pages', $maxItems);
|
||||
|
||||
// Combine them into a single array
|
||||
$allPages = array_merge($regularPages, $specificPages);
|
||||
|
||||
// Process each page to find suspicious deletions
|
||||
foreach ($allPages as $page) {
|
||||
if (isset($page['fr_page']) && isset($page['en_page'])) {
|
||||
// Calculate deletion percentage
|
||||
$enWordCount = (int)$page['en_page']['word_count'];
|
||||
|
@ -641,6 +727,11 @@ class WikiController extends AbstractController
|
|||
if ($wordDiff > 0 && $frWordCount > 0 && ($wordDiff / $enWordCount) > 0.3) {
|
||||
$page['deletion_percentage'] = round(($wordDiff / $enWordCount) * 100, 2);
|
||||
$wordDiffPages[] = $page;
|
||||
|
||||
// Limit the number of suspicious pages to prevent memory issues
|
||||
if (count($wordDiffPages) >= 20) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -794,8 +885,18 @@ class WikiController extends AbstractController
|
|||
return $this->redirectToRoute('app_admin_wiki');
|
||||
}
|
||||
|
||||
// Select a random page from the combined pages
|
||||
$randomPage = $allPages[array_rand($allPages)];
|
||||
// Filter pages to ensure they have an en_page key
|
||||
$validPages = array_filter($allPages, function($page) {
|
||||
return isset($page['en_page']) && !empty($page['en_page']);
|
||||
});
|
||||
|
||||
// If no valid pages, use any page but ensure we handle missing en_page in the template
|
||||
if (empty($validPages)) {
|
||||
$randomPage = $allPages[array_rand($allPages)];
|
||||
} else {
|
||||
// Select a random page from the valid pages
|
||||
$randomPage = $validPages[array_rand($validPages)];
|
||||
}
|
||||
|
||||
return $this->render('admin/wiki_random_suggestion.html.twig', [
|
||||
'page' => $randomPage
|
||||
|
@ -902,7 +1003,97 @@ EOT;
|
|||
]);
|
||||
}
|
||||
|
||||
#[Route('/wiki/archived-proposals', name: 'app_admin_wiki_archived_proposals')]
|
||||
#[Route('/wiki/dashboard', name: 'app_admin_wiki_dashboard')]
|
||||
public function dashboard(): Response
|
||||
{
|
||||
// Get metrics data from JSON files
|
||||
$metricsData = $this->getDashboardMetrics();
|
||||
|
||||
return $this->render('admin/wiki_dashboard.html.twig', [
|
||||
'metrics' => $metricsData
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get metrics data for the dashboard
|
||||
*
|
||||
* @return array Metrics data
|
||||
*/
|
||||
private function getDashboardMetrics(): array
|
||||
{
|
||||
$metrics = [
|
||||
'average_scores' => [],
|
||||
'tracked_pages' => [],
|
||||
'orphaned_pages' => [],
|
||||
'uncategorized_pages' => [],
|
||||
'dates' => []
|
||||
];
|
||||
|
||||
// Get data from outdated_pages.json for average scores and tracked pages
|
||||
$outdatedPagesFile = $this->getParameter('kernel.project_dir') . '/wiki_compare/outdated_pages.json';
|
||||
if (file_exists($outdatedPagesFile)) {
|
||||
$historyEntries = $this->extractJsonArrayByKey($outdatedPagesFile, 'history', 100);
|
||||
|
||||
// Process history entries
|
||||
foreach ($historyEntries as $date => $entry) {
|
||||
if (isset($entry['global_metrics'])) {
|
||||
$globalMetrics = $entry['global_metrics'];
|
||||
|
||||
// Format date for display
|
||||
$formattedDate = (new \DateTime($date))->format('Y-m-d');
|
||||
$metrics['dates'][] = $formattedDate;
|
||||
|
||||
// Get average staleness score
|
||||
$metrics['average_scores'][] = $globalMetrics['avg_staleness'] ?? 0;
|
||||
|
||||
// Get number of tracked pages
|
||||
$totalPages = $globalMetrics['total_pages'] ?? 0;
|
||||
$metrics['tracked_pages'][] = $totalPages;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get data from deadend_pages.json for uncategorized pages
|
||||
$deadendPagesFile = $this->getParameter('kernel.project_dir') . '/wiki_compare/deadend_pages.json';
|
||||
if (file_exists($deadendPagesFile)) {
|
||||
$historyEntries = $this->extractJsonArrayByKey($deadendPagesFile, 'history', 100);
|
||||
|
||||
// Process history entries
|
||||
foreach ($historyEntries as $date => $entry) {
|
||||
// Format date for display
|
||||
$formattedDate = (new \DateTime($date))->format('Y-m-d');
|
||||
|
||||
// If date already exists in metrics, use the same index
|
||||
$dateIndex = array_search($formattedDate, $metrics['dates']);
|
||||
if ($dateIndex === false) {
|
||||
$dateIndex = count($metrics['dates']);
|
||||
$metrics['dates'][] = $formattedDate;
|
||||
|
||||
// Add placeholder values for other metrics if this is a new date
|
||||
if (!isset($metrics['average_scores'][$dateIndex])) {
|
||||
$metrics['average_scores'][$dateIndex] = 0;
|
||||
}
|
||||
if (!isset($metrics['tracked_pages'][$dateIndex])) {
|
||||
$metrics['tracked_pages'][$dateIndex] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Get number of uncategorized pages
|
||||
$uncategorizedCount = isset($entry['pages']) ? count($entry['pages']) : 0;
|
||||
$metrics['uncategorized_pages'][$dateIndex] = $uncategorizedCount;
|
||||
}
|
||||
}
|
||||
|
||||
// Sort dates and reindex arrays
|
||||
array_multisort($metrics['dates'], SORT_ASC,
|
||||
$metrics['average_scores'],
|
||||
$metrics['tracked_pages'],
|
||||
$metrics['uncategorized_pages']);
|
||||
|
||||
return $metrics;
|
||||
}
|
||||
|
||||
#[Route('/wiki/archived-proposals', name: 'app_admin_wiki_archived_proposals')]
|
||||
public function archivedProposals(\Symfony\Component\HttpFoundation\Request $request): Response
|
||||
{
|
||||
$jsonFile = $this->getParameter('kernel.project_dir') . '/wiki_compare/archived_proposals.json';
|
||||
|
@ -1234,6 +1425,20 @@ EOT;
|
|||
}
|
||||
}
|
||||
|
||||
// Load deadend pages (pages starting with "France" from the DeadendPages list)
|
||||
$deadendPages = [];
|
||||
$categorizedPages = [];
|
||||
$deadendPagesFile = $this->getParameter('kernel.project_dir') . '/wiki_compare/deadend_pages.json';
|
||||
if (file_exists($deadendPagesFile)) {
|
||||
$deadendPagesData = json_decode(file_get_contents($deadendPagesFile), true);
|
||||
if (isset($deadendPagesData['pages']) && is_array($deadendPagesData['pages'])) {
|
||||
$deadendPages = $deadendPagesData['pages'];
|
||||
}
|
||||
if (isset($deadendPagesData['categorized_pages']) && is_array($deadendPagesData['categorized_pages'])) {
|
||||
$categorizedPages = $deadendPagesData['categorized_pages'];
|
||||
}
|
||||
}
|
||||
|
||||
return $this->render('admin/wiki.html.twig', [
|
||||
'wiki_pages' => $wikiPages,
|
||||
'missing_translations' => $missingTranslations,
|
||||
|
@ -1244,7 +1449,9 @@ EOT;
|
|||
'staleness_stats' => $stalenessStats,
|
||||
'wiki_pages_stats' => $wikiPagesStats,
|
||||
'available_translations' => $availableTranslations,
|
||||
'keys_without_wiki' => $keysWithoutWiki
|
||||
'keys_without_wiki' => $keysWithoutWiki,
|
||||
'deadend_pages' => $deadendPages,
|
||||
'categorized_pages' => $categorizedPages
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
|
||||
{% block body %}
|
||||
<div class="container mt-4">
|
||||
{% include 'admin/_wiki_navigation.html.twig' %}
|
||||
|
||||
<h1>Pages Wiki OpenStreetMap</h1>
|
||||
<p class="lead">Outil de qualité des des pages wiki OpenStreetMap en français et en anglais pour les clés OSM
|
||||
|
@ -570,6 +569,101 @@
|
|||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if deadend_pages is defined and deadend_pages|length > 0 %}
|
||||
<div class="card mb-4">
|
||||
<div class="card-header bg-danger text-white">
|
||||
<h2>Pages "France" sans catégorie ({{ deadend_pages|length }})</h2>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p>Ces pages wiki commençant par "France" n'ont pas de catégorie. Vous pouvez contribuer en ajoutant des catégories à ces pages.</p>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-hover">
|
||||
<thead class="thead-dark">
|
||||
<tr>
|
||||
<th>Titre</th>
|
||||
<th>Catégories suggérées</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for page in deadend_pages %}
|
||||
<tr>
|
||||
<td>
|
||||
<strong>{{ page.title }}</strong>
|
||||
</td>
|
||||
<td>
|
||||
{% if page.suggested_categories is defined and page.suggested_categories|length > 0 %}
|
||||
{% for category in page.suggested_categories %}
|
||||
<span class="badge bg-info me-1">{{ category }}</span>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<span class="text-muted">Aucune suggestion</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<div class="btn-group" role="group">
|
||||
<a href="{{ page.url }}" target="_blank"
|
||||
class="btn btn-sm btn-outline-primary" title="Voir la page">
|
||||
<i class="bi bi-eye"></i> Voir
|
||||
</a>
|
||||
<a href="{{ page.url }}?action=edit" target="_blank"
|
||||
class="btn btn-sm btn-success" title="Ajouter des catégories">
|
||||
<i class="bi bi-tags"></i> Ajouter catégories
|
||||
</a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if categorized_pages is defined and categorized_pages|length > 0 %}
|
||||
<div class="card mb-4">
|
||||
<div class="card-header bg-success text-white">
|
||||
<h2>Pages "France" récemment catégorisées ({{ categorized_pages|length }})</h2>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p>Ces pages wiki commençant par "France" ont été récemment catégorisées et ne sont plus dans la liste des pages sans catégorie.</p>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-hover">
|
||||
<thead class="thead-dark">
|
||||
<tr>
|
||||
<th>Titre</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for page in categorized_pages %}
|
||||
<tr>
|
||||
<td>
|
||||
<div class="d-flex align-items-center">
|
||||
<div>
|
||||
<strong>{{ page.title }}</strong>
|
||||
<span class="badge bg-success">Catégorisée</span>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<div class="btn-group" role="group">
|
||||
<a href="{{ page.url }}" target="_blank"
|
||||
class="btn btn-sm btn-outline-primary" title="Voir la page">
|
||||
<i class="bi bi-eye"></i> Voir
|
||||
</a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<p>
|
||||
le score de fraîcheur prend en compte d'avantage la différence entre le nombre de mots que l'ancienneté de
|
||||
modification.
|
||||
|
|
|
@ -128,7 +128,6 @@
|
|||
|
||||
{% block body %}
|
||||
<div class="container mt-4">
|
||||
{% include 'admin/_wiki_navigation.html.twig' %}
|
||||
|
||||
<h1>Propositions archivées OpenStreetMap</h1>
|
||||
<p class="lead">Analyse des votes sur les propositions archivées du wiki OSM</p>
|
||||
|
@ -182,7 +181,7 @@
|
|||
<div class="col-md-3 col-sm-6 mb-3">
|
||||
<div class="card stats-card h-100">
|
||||
<div class="card-body text-center">
|
||||
<div class="stats-value">{{ statistics.total_proposals }}</div>
|
||||
<div class="stats-value">{{ statistics.total_proposals|default(proposals|length) }}</div>
|
||||
<div class="stats-label">Propositions analysées</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -103,7 +103,6 @@
|
|||
}
|
||||
</style>
|
||||
<div class="container mt-4">
|
||||
{% include 'admin/_wiki_navigation.html.twig' %}
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<div>
|
||||
|
|
|
@ -67,7 +67,6 @@
|
|||
|
||||
{% block body %}
|
||||
<div class="container-fluid mt-4">
|
||||
{% include 'admin/_wiki_navigation.html.twig' %}
|
||||
|
||||
<h1>Créer une traduction française pour "{{ key }}"</h1>
|
||||
<p class="lead">Utilisez cette page pour traduire la page wiki en français. La page anglaise est affichée à gauche pour référence, et le formulaire d'édition de la page française est à droite.</p>
|
||||
|
|
254
templates/admin/wiki_dashboard.html.twig
Normal file
254
templates/admin/wiki_dashboard.html.twig
Normal file
|
@ -0,0 +1,254 @@
|
|||
{% extends 'base.html.twig' %}
|
||||
|
||||
{% block title %}Tableau de bord - Wiki OSM{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<div class="container mt-4">
|
||||
<h1>Tableau de bord - Wiki OSM</h1>
|
||||
<p class="lead">Suivi de l'évolution des métriques du wiki OpenStreetMap</p>
|
||||
|
||||
<div class="row mb-4">
|
||||
<div class="col-md-6">
|
||||
<div class="card h-100">
|
||||
<div class="card-header bg-primary text-white">
|
||||
<h2>Évolution du score moyen de décrépitude</h2>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<canvas id="averageScoreChart" height="300"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="card h-100">
|
||||
<div class="card-header bg-success text-white">
|
||||
<h2>Évolution du nombre de pages suivies</h2>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<canvas id="trackedPagesChart" height="300"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-4">
|
||||
<div class="col-md-6">
|
||||
<div class="card h-100">
|
||||
<div class="card-header bg-danger text-white">
|
||||
<h2>Évolution des pages sans catégorie</h2>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<canvas id="uncategorizedPagesChart" height="300"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="card h-100">
|
||||
<div class="card-header bg-info text-white">
|
||||
<h2>Statistiques actuelles</h2>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<div class="card bg-light">
|
||||
<div class="card-body text-center">
|
||||
<h3 class="card-title">Score moyen</h3>
|
||||
<p class="display-4">
|
||||
{% if metrics.average_scores|length > 0 %}
|
||||
{{ metrics.average_scores|last|number_format(1) }}
|
||||
{% else %}
|
||||
0
|
||||
{% endif %}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<div class="card bg-light">
|
||||
<div class="card-body text-center">
|
||||
<h3 class="card-title">Pages suivies</h3>
|
||||
<p class="display-4">
|
||||
{% if metrics.tracked_pages|length > 0 %}
|
||||
{{ metrics.tracked_pages|last }}
|
||||
{% else %}
|
||||
0
|
||||
{% endif %}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<div class="card bg-light">
|
||||
<div class="card-body text-center">
|
||||
<h3 class="card-title">Pages sans catégorie</h3>
|
||||
<p class="display-4">
|
||||
{% if metrics.uncategorized_pages|length > 0 %}
|
||||
{{ metrics.uncategorized_pages|last }}
|
||||
{% else %}
|
||||
0
|
||||
{% endif %}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<div class="card bg-light">
|
||||
<div class="card-body text-center">
|
||||
<h3 class="card-title">Dernière mise à jour</h3>
|
||||
<p class="display-4">
|
||||
{% if metrics.dates|length > 0 %}
|
||||
{{ metrics.dates|last }}
|
||||
{% else %}
|
||||
-
|
||||
{% endif %}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-header bg-secondary text-white">
|
||||
<h2>Données brutes</h2>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Date</th>
|
||||
<th>Score moyen</th>
|
||||
<th>Pages suivies</th>
|
||||
<th>Pages sans catégorie</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for i in 0..(metrics.dates|length - 1) %}
|
||||
<tr>
|
||||
<td>{{ metrics.dates[i] }}</td>
|
||||
<td>{{ metrics.average_scores[i]|default(0)|number_format(1) }}</td>
|
||||
<td>{{ metrics.tracked_pages[i]|default(0) }}</td>
|
||||
<td>{{ metrics.uncategorized_pages[i]|default(0) }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block javascripts %}
|
||||
{{ parent() }}
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Data from PHP
|
||||
const dates = {{ metrics.dates|json_encode|raw }};
|
||||
const averageScores = {{ metrics.average_scores|json_encode|raw }};
|
||||
const trackedPages = {{ metrics.tracked_pages|json_encode|raw }};
|
||||
const uncategorizedPages = {{ metrics.uncategorized_pages|json_encode|raw }};
|
||||
|
||||
// Average Score Chart
|
||||
const averageScoreCtx = document.getElementById('averageScoreChart').getContext('2d');
|
||||
new Chart(averageScoreCtx, {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: dates,
|
||||
datasets: [{
|
||||
label: 'Score moyen de décrépitude',
|
||||
data: averageScores,
|
||||
backgroundColor: 'rgba(54, 162, 235, 0.2)',
|
||||
borderColor: 'rgba(54, 162, 235, 1)',
|
||||
borderWidth: 2,
|
||||
tension: 0.1
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true
|
||||
}
|
||||
},
|
||||
plugins: {
|
||||
title: {
|
||||
display: true,
|
||||
text: 'Évolution du score moyen de décrépitude au fil du temps'
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Tracked Pages Chart
|
||||
const trackedPagesCtx = document.getElementById('trackedPagesChart').getContext('2d');
|
||||
new Chart(trackedPagesCtx, {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: dates,
|
||||
datasets: [{
|
||||
label: 'Nombre de pages suivies',
|
||||
data: trackedPages,
|
||||
backgroundColor: 'rgba(75, 192, 192, 0.2)',
|
||||
borderColor: 'rgba(75, 192, 192, 1)',
|
||||
borderWidth: 2,
|
||||
tension: 0.1
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true
|
||||
}
|
||||
},
|
||||
plugins: {
|
||||
title: {
|
||||
display: true,
|
||||
text: 'Évolution du nombre de pages suivies au fil du temps'
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Uncategorized Pages Chart
|
||||
const uncategorizedPagesCtx = document.getElementById('uncategorizedPagesChart').getContext('2d');
|
||||
new Chart(uncategorizedPagesCtx, {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: dates,
|
||||
datasets: [{
|
||||
label: 'Nombre de pages sans catégorie',
|
||||
data: uncategorizedPages,
|
||||
backgroundColor: 'rgba(255, 99, 132, 0.2)',
|
||||
borderColor: 'rgba(255, 99, 132, 1)',
|
||||
borderWidth: 2,
|
||||
tension: 0.1
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true
|
||||
}
|
||||
},
|
||||
plugins: {
|
||||
title: {
|
||||
display: true,
|
||||
text: 'Évolution du nombre de pages sans catégorie au fil du temps'
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
|
@ -4,7 +4,6 @@
|
|||
|
||||
{% block body %}
|
||||
<div class="container mt-4">
|
||||
{% include 'admin/_wiki_navigation.html.twig' %}
|
||||
|
||||
<h1>Évolution des scores de décrépitude</h1>
|
||||
<p class="lead">
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
|
||||
{% block body %}
|
||||
<div class="container mt-4">
|
||||
{% include 'admin/_wiki_navigation.html.twig' %}
|
||||
|
||||
<h1>Pages Wiki françaises sans traduction anglaise</h1>
|
||||
<p class="lead">Liste des pages françaises du wiki OSM qui n'ont pas de traduction en anglais.</p>
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
|
||||
{% block body %}
|
||||
<div class="container mt-4">
|
||||
{% include 'admin/_wiki_navigation.html.twig' %}
|
||||
|
||||
<h1>Groupes OSM-FR</h1>
|
||||
<p class="lead">Liste des groupes de travail et des groupes locaux d'OpenStreetMap France.</p>
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
|
||||
{% block body %}
|
||||
<div class="container mt-4">
|
||||
{% include 'admin/_wiki_navigation.html.twig' %}
|
||||
|
||||
<h1>Pages Wiki non disponibles en français</h1>
|
||||
<p class="lead">Liste des pages du wiki OSM qui n'ont pas de traduction française, groupées par langue d'origine.</p>
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
|
||||
{% block body %}
|
||||
<div class="container mt-4">
|
||||
{% include 'admin/_wiki_navigation.html.twig' %}
|
||||
|
||||
<h1>Suggestion de page Wiki à améliorer</h1>
|
||||
<p class="lead">Voici une page wiki qui a besoin d'être améliorée.</p>
|
||||
|
@ -25,28 +24,34 @@
|
|||
<div class="card-header bg-primary text-white">
|
||||
<h3>Version anglaise</h3>
|
||||
<p class="mb-0">
|
||||
<small>Dernière modification: {{ page.en_page.last_modified }}</small>
|
||||
<small>Dernière modification: {{ page.en_page is defined and page.en_page.last_modified is defined ? page.en_page.last_modified : 'Non disponible' }}</small>
|
||||
</p>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<ul class="list-group mb-3">
|
||||
<li class="list-group-item d-flex justify-content-between align-items-center">
|
||||
Sections
|
||||
<span class="badge bg-primary rounded-pill">{{ page.en_page.sections }}</span>
|
||||
<span class="badge bg-primary rounded-pill">{{ page.en_page is defined ? page.en_page.sections|default(0) : 0 }}</span>
|
||||
</li>
|
||||
<li class="list-group-item d-flex justify-content-between align-items-center">
|
||||
Mots
|
||||
<span class="badge bg-primary rounded-pill">{{ page.en_page.word_count|default(0) }}</span>
|
||||
<span class="badge bg-primary rounded-pill">{{ page.en_page is defined ? page.en_page.word_count|default(0) : 0 }}</span>
|
||||
</li>
|
||||
<li class="list-group-item d-flex justify-content-between align-items-center">
|
||||
Liens
|
||||
<span class="badge bg-primary rounded-pill">{{ page.en_page.link_count|default(0) }}</span>
|
||||
<span class="badge bg-primary rounded-pill">{{ page.en_page is defined ? page.en_page.link_count|default(0) : 0 }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="d-grid gap-2">
|
||||
<a href="{{ page.en_page.url }}" target="_blank" class="btn btn-outline-primary">
|
||||
<i class="bi bi-box-arrow-up-right"></i> Voir la page anglaise
|
||||
</a>
|
||||
{% if page.en_page is defined and page.en_page.url is defined %}
|
||||
<a href="{{ page.en_page.url }}" target="_blank" class="btn btn-outline-primary">
|
||||
<i class="bi bi-box-arrow-up-right"></i> Voir la page anglaise
|
||||
</a>
|
||||
{% else %}
|
||||
<button class="btn btn-outline-secondary" disabled>
|
||||
<i class="bi bi-box-arrow-up-right"></i> URL non disponible
|
||||
</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
|
||||
{% block body %}
|
||||
<div class="container mt-4">
|
||||
{% include 'admin/_wiki_navigation.html.twig' %}
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<h1>Évolution des classements Wiki OSM</h1>
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
|
||||
{% block body %}
|
||||
<div class="container mt-4">
|
||||
{% include 'admin/_wiki_navigation.html.twig' %}
|
||||
|
||||
<h1>Changements récents Wiki OpenStreetMap</h1>
|
||||
<p class="lead">Liste des changements récents dans l'espace de noms français du wiki OpenStreetMap.</p>
|
||||
|
@ -26,18 +25,27 @@
|
|||
{% for member in team_members %}
|
||||
<div class="col-md-4 mb-3">
|
||||
<div class="d-flex align-items-center">
|
||||
<a href="{{ member.user_url }}" target="_blank" class="text-decoration-none">
|
||||
<span class="fw-bold">{{ member.username }}</span>
|
||||
</a>
|
||||
<span class="badge bg-primary ms-2">{{ member.contributions }}</span>
|
||||
<div class="ms-2 small">
|
||||
<span class="text-success" title="Caractères ajoutés">+{{ member.chars_added }}</span>
|
||||
{% if member.chars_changed > 0 %}
|
||||
<span class="text-warning" title="Caractères modifiés">~{{ member.chars_changed }}</span>
|
||||
{% endif %}
|
||||
{% if member.chars_deleted > 0 %}
|
||||
<span class="text-danger" title="Caractères supprimés">-{{ member.chars_deleted }}</span>
|
||||
{% endif %}
|
||||
{% if member.avatar_url is defined and member.avatar_url %}
|
||||
<img src="{{ member.avatar_url }}" alt="{{ member.username }}" class="rounded-circle me-2" style="width: 40px; height: 40px; object-fit: cover;">
|
||||
{% else %}
|
||||
<div class="rounded-circle bg-secondary text-white d-flex align-items-center justify-content-center me-2" style="width: 40px; height: 40px;">
|
||||
<span>{{ member.username|first|upper }}</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div>
|
||||
<a href="{{ member.user_url }}" target="_blank" class="text-decoration-none">
|
||||
<span class="fw-bold">{{ member.username }}</span>
|
||||
</a>
|
||||
<span class="badge bg-primary ms-2">{{ member.contributions }}</span>
|
||||
<div class="small">
|
||||
<span class="text-success" title="Caractères ajoutés">+{{ member.chars_added }}</span>
|
||||
{% if member.chars_changed > 0 %}
|
||||
<span class="text-warning" title="Caractères modifiés">~{{ member.chars_changed }}</span>
|
||||
{% endif %}
|
||||
{% if member.chars_deleted > 0 %}
|
||||
<span class="text-danger" title="Caractères supprimés">-{{ member.chars_deleted }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -83,13 +91,22 @@
|
|||
</td>
|
||||
<td>{{ change.timestamp }}</td>
|
||||
<td>
|
||||
{% if change.user_url %}
|
||||
<a href="{{ change.user_url }}" target="_blank" class="text-decoration-none">
|
||||
<div class="d-flex align-items-center">
|
||||
{% if change.avatar_url is defined and change.avatar_url %}
|
||||
<img src="{{ change.avatar_url }}" alt="{{ change.user }}" class="rounded-circle me-2" style="width: 30px; height: 30px; object-fit: cover;">
|
||||
{% else %}
|
||||
<div class="rounded-circle bg-secondary text-white d-flex align-items-center justify-content-center me-2" style="width: 30px; height: 30px; font-size: 0.8rem;">
|
||||
<span>{{ change.user|first|upper }}</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if change.user_url %}
|
||||
<a href="{{ change.user_url }}" target="_blank" class="text-decoration-none">
|
||||
{{ change.user }}
|
||||
</a>
|
||||
{% else %}
|
||||
{{ change.user }}
|
||||
</a>
|
||||
{% else %}
|
||||
{{ change.user }}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
{{ change.comment }}
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
|
||||
{% block body %}
|
||||
<div class="container mt-4">
|
||||
{% include 'admin/_wiki_navigation.html.twig' %}
|
||||
|
||||
<h1>Pages Wiki avec suppressions suspectes</h1>
|
||||
|
||||
|
|
|
@ -64,7 +64,6 @@
|
|||
|
||||
{% block body %}
|
||||
<div class="container mt-4">
|
||||
{% include 'admin/_wiki_navigation.html.twig' %}
|
||||
|
||||
<h1>Propositions de tags OSM</h1>
|
||||
<p class="lead">Liste des propositions de tags OpenStreetMap actuellement en cours de vote ou récemment modifiées.</p>
|
||||
|
|
|
@ -21,97 +21,141 @@
|
|||
{% endblock %}
|
||||
</head>
|
||||
<body>
|
||||
<header class="main-header">
|
||||
<div class="container">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-12">
|
||||
<a href="{{ path('app_public_index') }}" class="d-flex align-items-center">
|
||||
<h1 class="mb-0 mt-2">
|
||||
<img src="{{ asset('logo-osm.png') }}" alt="Logo OSM" class="me-2" style="width: 30px; height: 30px;">
|
||||
Qualiwiki OpenStreetMap</h1>
|
||||
</a>
|
||||
</div>
|
||||
<div class="d-flex">
|
||||
<!-- Dark Sidebar -->
|
||||
<div class="sidebar bg-dark text-white" id="sidebar">
|
||||
{# {% include 'admin/_wiki_navigation.html.twig' %}#}
|
||||
|
||||
<div class="sidebar-header p-3">
|
||||
<a href="{{ path('app_public_index') }}" class="d-flex align-items-center text-white text-decoration-none">
|
||||
<img src="{{ asset('logo-osm.png') }}" alt="Logo OSM" class="me-2" style="width: 30px; height: 30px;">
|
||||
<h5 class="mb-0">Qualiwiki OSM</h5>
|
||||
</a>
|
||||
</div>
|
||||
{% for label, messages in app.flashes %}
|
||||
{% for message in messages %}
|
||||
<div class="alert alert-{{ label }} is-{{ label }} alert-dismissible fade show mt-3" role="alert">
|
||||
{{ message }}
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
<div class="sidebar-content p-2">
|
||||
<ul class="nav flex-column">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link text-white {% if app.request.get('_route') == 'app_public_index' %}active{% endif %}" href="{{ path('app_public_index') }}">
|
||||
<i class="bi bi-house-fill"></i>
|
||||
{{ 'accueil'|trans }}
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link text-white {% if app.request.get('_route') == 'app_admin_wiki_dashboard' %}active{% endif %}" href="{{ path('app_admin_wiki_dashboard') }}">
|
||||
<i class="bi bi-speedometer2"></i>
|
||||
Tableau de bord
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link text-white {% if app.request.get('_route') == 'app_admin_wiki_decrepitude' %}active{% endif %}" href="{{ path('app_admin_wiki_decrepitude') }}">
|
||||
<i class="bi bi-graph-up"></i>
|
||||
Scores de décrépitude
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link text-white {% if app.request.get('_route') == 'app_admin_wiki' %}active{% endif %}" href="{{ path('app_admin_wiki') }}">
|
||||
<i class="bi bi-list-ul"></i>
|
||||
Pages Wiki
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link text-white {% if app.request.get('_route') == 'app_admin_wiki_archived_proposals' %}active{% endif %}" href="{{ path('app_admin_wiki_archived_proposals') }}">
|
||||
<i class="bi bi-archive"></i>
|
||||
Propositions archivées
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link text-white {% if app.request.get('_route') == 'app_admin_wiki_random_suggestion' %}active{% endif %}" href="{{ path('app_admin_wiki_random_suggestion') }}">
|
||||
<i class="bi bi-shuffle"></i>
|
||||
Suggestion aléatoire
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link text-white {% if app.request.get('_route') == 'app_admin_wiki_recent_changes' %}active{% endif %}" href="{{ path('app_admin_wiki_recent_changes') }}">
|
||||
<i class="bi bi-clock-history"></i>
|
||||
Changements récents
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link text-white {% if app.request.get('_route') == 'app_admin_wiki_suspicious_deletions' %}active{% endif %}" href="{{ path('app_admin_wiki_suspicious_deletions') }}">
|
||||
<i class="bi bi-exclamation-triangle"></i>
|
||||
Suppressions suspectes
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Main Content -->
|
||||
<div class="content-wrapper">
|
||||
<header class="main-header">
|
||||
<div class="container">
|
||||
{% for label, messages in app.flashes %}
|
||||
{% for message in messages %}
|
||||
<div class="alert alert-{{ label }} is-{{ label }} alert-dismissible fade show mt-3" role="alert">
|
||||
{{ message }}
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="body-landing">
|
||||
{% block body %}{% endblock %}
|
||||
</main>
|
||||
|
||||
<footer class="main-footer">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<p class="mb-2">OpenStreetMap Mon Commerce</p>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4 col-12">
|
||||
<p class="mb-2">
|
||||
Licence AGPLv3+, fait par
|
||||
<a href="https://mastodon.cipherbliss.com/@tykayn">Tykayn</a> de
|
||||
<a href="https://www.cipherbliss.com">CipherBliss EI</a>,
|
||||
membre de la fédération des professionels d'OpenStreetMap
|
||||
</p>
|
||||
<p class="mb-2">
|
||||
<a href="https://www.openstreetmap.org/copyright">OpenStreetMap France</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4 col-12">
|
||||
<div id="userChangesHistory"></div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4 col-12">
|
||||
<div id="qr-share" class="mb-12">
|
||||
partagez cette page :
|
||||
<br>
|
||||
<div id="qrcode"></div>
|
||||
</div>
|
||||
<p class="mb-0">
|
||||
<a href="https://www.openstreetmap.org/copyright">Sources du logiciel</a>
|
||||
</p>
|
||||
<p class="mb-2">
|
||||
Sources des données : <a href="https://www.openstreetmap.org/">OpenStreetMap</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<p class="mb-2">
|
||||
<a href="https://forum.openstreetmap.fr/t/osm-mon-commerce/34403/11" class="btn btn-outline-info ms-auto suggestion-float-btn" target="_blank" rel="noopener">
|
||||
<i class="bi bi-chat-dots"></i> Faire une suggestion
|
||||
</a>
|
||||
<a href="https://osm-commerces.cipherbliss.com/api/v1/stats_geojson" target="_blank">Documentation de l'API (GeoJSON)</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
<div class="row mt-3">
|
||||
<div class="col-12">
|
||||
{% include 'public/nav.html.twig' %}
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
|
||||
|
||||
<main class="body-landing">
|
||||
{% block body %}{% endblock %}
|
||||
</main>
|
||||
|
||||
<footer class="main-footer">
|
||||
<div class="container">
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
{% include 'public/nav.html.twig' %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<p class="mb-2">OpenStreetMap Mon Commerce</p>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4 col-12">
|
||||
<p class="mb-2">
|
||||
Licence AGPLv3+, fait par
|
||||
<a href="https://mastodon.cipherbliss.com/@tykayn">Tykayn</a> de
|
||||
<a href="https://www.cipherbliss.com">CipherBliss EI</a>,
|
||||
membre de la fédération des professionels d'OpenStreetMap
|
||||
</p>
|
||||
<p class="mb-2">
|
||||
<a href="https://www.openstreetmap.org/copyright">OpenStreetMap France</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4 col-12">
|
||||
|
||||
<div id="userChangesHistory"></div>
|
||||
</div>
|
||||
<div class="col-md-4 col-12">
|
||||
<div id="qr-share" class="mb-12">
|
||||
partagez cette page :
|
||||
<br>
|
||||
<div id="qrcode"></div>
|
||||
</div>
|
||||
<div class="col-md-4 col-12">
|
||||
|
||||
<p class="mb-0">
|
||||
<a href="https://www.openstreetmap.org/copyright">Sources du logiciel</a>
|
||||
</p>
|
||||
<p class="mb-2">
|
||||
Sources des données : <a href="https://www.openstreetmap.org/">OpenStreetMap</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<p class="mb-2">
|
||||
<a href="https://forum.openstreetmap.fr/t/osm-mon-commerce/34403/11" class="btn btn-outline-info ms-auto suggestion-float-btn" target="_blank" rel="noopener">
|
||||
<i class="bi bi-chat-dots"></i> Faire une suggestion
|
||||
</a>
|
||||
|
||||
<a href="https://osm-commerces.cipherbliss.com/api/v1/stats_geojson" target="_blank">Documentation de l'API (GeoJSON)</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
{% block javascripts %}
|
||||
{{ encore_entry_script_tags('app') }}
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
{% block body %}
|
||||
<div class="container mt-4">
|
||||
{% include 'admin/_wiki_navigation.html.twig' %}
|
||||
|
||||
|
||||
<h1>Pages Wiki OpenStreetMap</h1>
|
||||
<p class="lead">Outil de qualité des des pages wiki OpenStreetMap en français et en anglais pour les clés OSM
|
||||
|
|
|
@ -25,28 +25,34 @@
|
|||
<div class="card-header bg-primary text-white">
|
||||
<h3>Version anglaise</h3>
|
||||
<p class="mb-0">
|
||||
<small>Dernière modification: {{ page.en_page.last_modified }}</small>
|
||||
<small>Dernière modification: {{ page.en_page is defined and page.en_page.last_modified is defined ? page.en_page.last_modified : 'Non disponible' }}</small>
|
||||
</p>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<ul class="list-group mb-3">
|
||||
<li class="list-group-item d-flex justify-content-between align-items-center">
|
||||
Sections
|
||||
<span class="badge bg-primary rounded-pill">{{ page.en_page.sections }}</span>
|
||||
<span class="badge bg-primary rounded-pill">{{ page.en_page is defined ? page.en_page.sections|default(0) : 0 }}</span>
|
||||
</li>
|
||||
<li class="list-group-item d-flex justify-content-between align-items-center">
|
||||
Mots
|
||||
<span class="badge bg-primary rounded-pill">{{ page.en_page.word_count|default(0) }}</span>
|
||||
<span class="badge bg-primary rounded-pill">{{ page.en_page is defined ? page.en_page.word_count|default(0) : 0 }}</span>
|
||||
</li>
|
||||
<li class="list-group-item d-flex justify-content-between align-items-center">
|
||||
Liens
|
||||
<span class="badge bg-primary rounded-pill">{{ page.en_page.link_count|default(0) }}</span>
|
||||
<span class="badge bg-primary rounded-pill">{{ page.en_page is defined ? page.en_page.link_count|default(0) : 0 }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="d-grid gap-2">
|
||||
<a href="{{ page.en_page.url }}" target="_blank" class="btn btn-outline-primary">
|
||||
<i class="bi bi-box-arrow-up-right"></i> Voir la page anglaise
|
||||
</a>
|
||||
{% if page.en_page is defined and page.en_page.url is defined %}
|
||||
<a href="{{ page.en_page.url }}" target="_blank" class="btn btn-outline-primary">
|
||||
<i class="bi bi-box-arrow-up-right"></i> Voir la page anglaise
|
||||
</a>
|
||||
{% else %}
|
||||
<button class="btn btn-outline-secondary" disabled>
|
||||
<i class="bi bi-box-arrow-up-right"></i> URL non disponible
|
||||
</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -26,18 +26,27 @@
|
|||
{% for member in team_members %}
|
||||
<div class="col-md-4 mb-3">
|
||||
<div class="d-flex align-items-center">
|
||||
<a href="{{ member.user_url }}" target="_blank" class="text-decoration-none">
|
||||
<span class="fw-bold">{{ member.username }}</span>
|
||||
</a>
|
||||
<span class="badge bg-primary ms-2">{{ member.contributions }}</span>
|
||||
<div class="ms-2 small">
|
||||
<span class="text-success" title="Caractères ajoutés">+{{ member.chars_added }}</span>
|
||||
{% if member.chars_changed > 0 %}
|
||||
<span class="text-warning" title="Caractères modifiés">~{{ member.chars_changed }}</span>
|
||||
{% endif %}
|
||||
{% if member.chars_deleted > 0 %}
|
||||
<span class="text-danger" title="Caractères supprimés">-{{ member.chars_deleted }}</span>
|
||||
{% endif %}
|
||||
{% if member.avatar_url is defined and member.avatar_url %}
|
||||
<img src="{{ member.avatar_url }}" alt="{{ member.username }}" class="rounded-circle me-2" style="width: 40px; height: 40px; object-fit: cover;">
|
||||
{% else %}
|
||||
<div class="rounded-circle bg-secondary text-white d-flex align-items-center justify-content-center me-2" style="width: 40px; height: 40px;">
|
||||
<span>{{ member.username|first|upper }}</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div>
|
||||
<a href="{{ member.user_url }}" target="_blank" class="text-decoration-none">
|
||||
<span class="fw-bold">{{ member.username }}</span>
|
||||
</a>
|
||||
<span class="badge bg-primary ms-2">{{ member.contributions }}</span>
|
||||
<div class="small">
|
||||
<span class="text-success" title="Caractères ajoutés">+{{ member.chars_added }}</span>
|
||||
{% if member.chars_changed > 0 %}
|
||||
<span class="text-warning" title="Caractères modifiés">~{{ member.chars_changed }}</span>
|
||||
{% endif %}
|
||||
{% if member.chars_deleted > 0 %}
|
||||
<span class="text-danger" title="Caractères supprimés">-{{ member.chars_deleted }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -83,13 +92,22 @@
|
|||
</td>
|
||||
<td>{{ change.timestamp }}</td>
|
||||
<td>
|
||||
{% if change.user_url %}
|
||||
<a href="{{ change.user_url }}" target="_blank" class="text-decoration-none">
|
||||
<div class="d-flex align-items-center">
|
||||
{% if change.avatar_url is defined and change.avatar_url %}
|
||||
<img src="{{ change.avatar_url }}" alt="{{ change.user }}" class="rounded-circle me-2" style="width: 30px; height: 30px; object-fit: cover;">
|
||||
{% else %}
|
||||
<div class="rounded-circle bg-secondary text-white d-flex align-items-center justify-content-center me-2" style="width: 30px; height: 30px; font-size: 0.8rem;">
|
||||
<span>{{ change.user|first|upper }}</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if change.user_url %}
|
||||
<a href="{{ change.user_url }}" target="_blank" class="text-decoration-none">
|
||||
{{ change.user }}
|
||||
</a>
|
||||
{% else %}
|
||||
{{ change.user }}
|
||||
</a>
|
||||
{% else %}
|
||||
{{ change.user }}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
{{ change.comment }}
|
||||
|
|
199
wiki_compare/CHANGES_DEADEND_PAGES.md
Normal file
199
wiki_compare/CHANGES_DEADEND_PAGES.md
Normal file
|
@ -0,0 +1,199 @@
|
|||
# Ajout de la fonctionnalité de suivi des pages "France" sans catégorie
|
||||
|
||||
## Description
|
||||
|
||||
Cette fonctionnalité permet de suivre les pages du wiki OpenStreetMap commençant par "France" qui n'ont pas de catégorie (pages sans issues). Ces pages sont identifiées à partir de la liste des "DeadendPages" du wiki OSM. La fonctionnalité suggère également des catégories pour ces pages et suit leur évolution lorsqu'elles sont catégorisées.
|
||||
|
||||
## Modifications apportées
|
||||
|
||||
### 1. Ajout de constantes dans `wiki_compare.py`
|
||||
|
||||
```python
|
||||
WIKI_DEADEND_PAGES_URL = "https://wiki.openstreetmap.org/w/index.php?title=Special:DeadendPages&limit=500&offset=1000"
|
||||
DEADEND_PAGES_FILE = "deadend_pages.json"
|
||||
```
|
||||
|
||||
### 2. Ajout de la fonction `suggest_categories` dans `wiki_compare.py`
|
||||
|
||||
Cette fonction analyse le titre et le contenu d'une page pour suggérer des catégories pertinentes :
|
||||
|
||||
```python
|
||||
def suggest_categories(page_title, page_url):
|
||||
"""
|
||||
Suggest categories for an uncategorized page based on its title and content
|
||||
|
||||
Args:
|
||||
page_title (str): Title of the page
|
||||
page_url (str): URL of the page
|
||||
|
||||
Returns:
|
||||
list: List of suggested categories
|
||||
"""
|
||||
# Logique de suggestion de catégories basée sur le titre et le contenu de la page
|
||||
```
|
||||
|
||||
### 3. Ajout de la fonction `fetch_deadend_pages` dans `wiki_compare.py`
|
||||
|
||||
Cette fonction récupère les pages commençant par "France" depuis la liste des DeadendPages :
|
||||
|
||||
```python
|
||||
def fetch_deadend_pages():
|
||||
"""
|
||||
Fetch pages starting with "France" from the DeadendPages list
|
||||
|
||||
Returns:
|
||||
list: List of dictionaries containing page information
|
||||
"""
|
||||
# Logique de récupération des pages depuis la liste des DeadendPages
|
||||
# Filtrage des pages commençant par "France"
|
||||
# Suggestion de catégories pour chaque page
|
||||
```
|
||||
|
||||
### 4. Modification de la fonction `main` dans `wiki_compare.py`
|
||||
|
||||
Ajout d'une section pour traiter les pages sans catégorie et suivre leur évolution :
|
||||
|
||||
```python
|
||||
# Fetch pages starting with "France" from the DeadendPages list
|
||||
deadend_pages = fetch_deadend_pages()
|
||||
|
||||
if deadend_pages:
|
||||
# Load existing deadend pages data to compare with history
|
||||
existing_data = load_json_data(DEADEND_PAGES_FILE)
|
||||
|
||||
# Initialize history if it doesn't exist
|
||||
if 'history' not in existing_data:
|
||||
existing_data['history'] = {}
|
||||
|
||||
# Get the most recent history entry
|
||||
sorted_timestamps = sorted(existing_data.get('history', {}).keys())
|
||||
previous_pages = []
|
||||
if sorted_timestamps:
|
||||
latest_timestamp = sorted_timestamps[-1]
|
||||
previous_pages = existing_data['history'][latest_timestamp].get('pages', [])
|
||||
|
||||
# Find pages that were in the previous list but are no longer in the current list
|
||||
previous_urls = [page['url'] for page in previous_pages]
|
||||
current_urls = [page['url'] for page in deadend_pages]
|
||||
|
||||
categorized_pages = []
|
||||
for url in previous_urls:
|
||||
if url not in current_urls:
|
||||
# Find the page in previous_pages
|
||||
for page in previous_pages:
|
||||
if page['url'] == url:
|
||||
# This page is no longer in the DeadendPages list, which means it has been categorized
|
||||
categorized_pages.append(page)
|
||||
break
|
||||
|
||||
# Create a timestamp for the current data
|
||||
current_timestamp = datetime.now().isoformat()
|
||||
|
||||
# Create the history entry
|
||||
history_entry = {
|
||||
'pages': deadend_pages,
|
||||
'categorized_pages': categorized_pages
|
||||
}
|
||||
|
||||
# Add the entry to history with timestamp as key
|
||||
existing_data['history'][current_timestamp] = history_entry
|
||||
|
||||
# Update the current data
|
||||
existing_data['pages'] = deadend_pages
|
||||
existing_data['categorized_pages'] = categorized_pages
|
||||
existing_data['last_updated'] = current_timestamp
|
||||
|
||||
# Save the updated data
|
||||
save_to_json(existing_data, DEADEND_PAGES_FILE)
|
||||
```
|
||||
|
||||
### 5. Modification du contrôleur `WikiController.php`
|
||||
|
||||
Ajout du chargement des données des pages sans catégorie :
|
||||
|
||||
```php
|
||||
// Load deadend pages (pages starting with "France" from the DeadendPages list)
|
||||
$deadendPages = [];
|
||||
$categorizedPages = [];
|
||||
$deadendPagesFile = $this->getParameter('kernel.project_dir') . '/wiki_compare/deadend_pages.json';
|
||||
if (file_exists($deadendPagesFile)) {
|
||||
$deadendPagesData = json_decode(file_get_contents($deadendPagesFile), true);
|
||||
if (isset($deadendPagesData['pages']) && is_array($deadendPagesData['pages'])) {
|
||||
$deadendPages = $deadendPagesData['pages'];
|
||||
}
|
||||
if (isset($deadendPagesData['categorized_pages']) && is_array($deadendPagesData['categorized_pages'])) {
|
||||
$categorizedPages = $deadendPagesData['categorized_pages'];
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Ajout des données à la vue :
|
||||
|
||||
```php
|
||||
return $this->render('admin/wiki.html.twig', [
|
||||
// Autres données...
|
||||
'deadend_pages' => $deadendPages,
|
||||
'categorized_pages' => $categorizedPages
|
||||
]);
|
||||
```
|
||||
|
||||
### 6. Ajout de sections dans le template `admin/wiki.html.twig`
|
||||
|
||||
Ajout d'une section pour afficher les pages sans catégorie :
|
||||
|
||||
```twig
|
||||
{% if deadend_pages is defined and deadend_pages|length > 0 %}
|
||||
<div class="card mb-4">
|
||||
<div class="card-header bg-danger text-white">
|
||||
<h2>Pages "France" sans catégorie ({{ deadend_pages|length }})</h2>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p>Ces pages wiki commençant par "France" n'ont pas de catégorie. Vous pouvez contribuer en ajoutant des catégories à ces pages.</p>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-hover">
|
||||
<!-- Contenu de la table -->
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
```
|
||||
|
||||
Ajout d'une section pour afficher les pages récemment catégorisées :
|
||||
|
||||
```twig
|
||||
{% if categorized_pages is defined and categorized_pages|length > 0 %}
|
||||
<div class="card mb-4">
|
||||
<div class="card-header bg-success text-white">
|
||||
<h2>Pages "France" récemment catégorisées ({{ categorized_pages|length }})</h2>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p>Ces pages wiki commençant par "France" ont été récemment catégorisées et ne sont plus dans la liste des pages sans catégorie.</p>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-hover">
|
||||
<!-- Contenu de la table -->
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
```
|
||||
|
||||
## Fonctionnalités
|
||||
|
||||
1. **Scraping des pages sans catégorie** : Récupération des pages commençant par "France" depuis la liste des DeadendPages du wiki OSM.
|
||||
2. **Suggestion de catégories** : Analyse du titre et du contenu des pages pour suggérer des catégories pertinentes.
|
||||
3. **Suivi des pages catégorisées** : Détection des pages qui ont été catégorisées depuis le dernier scraping.
|
||||
4. **Affichage dans l'interface** : Affichage des pages sans catégorie et des pages récemment catégorisées dans l'interface utilisateur.
|
||||
|
||||
## Utilisation
|
||||
|
||||
1. Le script `wiki_compare.py` est exécuté périodiquement pour mettre à jour les données.
|
||||
2. Les utilisateurs peuvent consulter les pages sans catégorie et les pages récemment catégorisées dans l'interface.
|
||||
3. Les utilisateurs peuvent cliquer sur les boutons d'action pour voir les pages et ajouter des catégories.
|
||||
|
||||
## Avantages
|
||||
|
||||
1. **Amélioration de la qualité du wiki** : Aide à identifier et à catégoriser les pages qui devraient avoir des catégories.
|
||||
2. **Suivi des progrès** : Permet de suivre les progrès dans la catégorisation des pages.
|
||||
3. **Suggestion de catégories** : Facilite le travail des contributeurs en suggérant des catégories pertinentes.
|
94
wiki_compare/CHANGES_FIXES.md
Normal file
94
wiki_compare/CHANGES_FIXES.md
Normal file
|
@ -0,0 +1,94 @@
|
|||
# Corrections apportées à QualiWiki
|
||||
|
||||
Ce document décrit les corrections apportées pour résoudre les problèmes mentionnés dans la description des issues.
|
||||
|
||||
## 1. Correction de l'erreur dans wiki_archived_proposals.html.twig
|
||||
|
||||
### Problème
|
||||
```
|
||||
Key "total_proposals" for sequence/mapping with keys "0, 1, 2, 3, ..." does not exist in admin/wiki_archived_proposals.html.twig at line 193.
|
||||
```
|
||||
|
||||
### Solution
|
||||
Ajout d'une valeur par défaut pour `total_proposals` dans le template :
|
||||
|
||||
```twig
|
||||
<div class="stats-value">{{ statistics.total_proposals|default(proposals|length) }}</div>
|
||||
```
|
||||
|
||||
Cette modification permet d'utiliser le nombre de propositions dans le tableau `proposals` comme valeur par défaut si `statistics.total_proposals` n'est pas défini.
|
||||
|
||||
## 2. Implémentation d'un panneau latéral sombre pour la navigation
|
||||
|
||||
### Modifications
|
||||
- Ajout d'un panneau latéral sombre dans `templates/base.html.twig`
|
||||
- Ajout de styles CSS dans `public/css/main.css` pour le panneau latéral
|
||||
- Réorganisation de la structure HTML pour intégrer le panneau latéral
|
||||
- Ajout de liens vers toutes les sections principales de l'application
|
||||
|
||||
### Avantages
|
||||
- Navigation plus claire et plus accessible
|
||||
- Meilleure organisation visuelle de l'application
|
||||
- Accès rapide à toutes les fonctionnalités principales
|
||||
|
||||
## 3. Création d'une page dashboard avec graphiques
|
||||
|
||||
### Fonctionnalités
|
||||
- Ajout d'une méthode `dashboard` dans `WikiController.php`
|
||||
- Création d'un template `admin/wiki_dashboard.html.twig`
|
||||
- Implémentation de graphiques montrant :
|
||||
- L'évolution du score moyen de décrépitude
|
||||
- L'évolution du nombre de pages suivies
|
||||
- L'évolution du nombre de pages sans catégorie
|
||||
|
||||
### Données suivies
|
||||
- Score moyen de décrépitude des pages
|
||||
- Nombre de pages suivies
|
||||
- Nombre de pages sans catégorie (pages orphelines)
|
||||
|
||||
## 4. Correction de l'erreur dans wiki_random_suggestion.html.twig
|
||||
|
||||
### Problème
|
||||
```
|
||||
Key "en_page" for sequence/mapping with keys "title, level" does not exist in admin/wiki_random_suggestion.html.twig at line 28.
|
||||
```
|
||||
|
||||
### Solution
|
||||
- Ajout de vérifications pour l'existence de `en_page` dans le template
|
||||
- Ajout de valeurs par défaut pour les propriétés de `en_page`
|
||||
- Filtrage des pages pour s'assurer qu'elles ont une clé `en_page` dans le contrôleur
|
||||
|
||||
## 5. Ajout d'avatars utilisateurs dans la page des changements récents
|
||||
|
||||
### Fonctionnalités
|
||||
- Ajout d'une fonction `fetchUserAvatar` dans `WikiController.php` pour récupérer et mettre en cache les avatars des utilisateurs
|
||||
- Mise à jour des templates `admin/wiki_recent_changes.html.twig` et `public/wiki_recent_changes.html.twig` pour afficher les avatars
|
||||
- Mise en cache des avatars pour éviter de les récupérer à chaque fois
|
||||
|
||||
## 6. Correction de l'erreur de mémoire dans la page des suppressions suspectes
|
||||
|
||||
### Problème
|
||||
```
|
||||
Error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 175260864 bytes)
|
||||
```
|
||||
|
||||
### Solution
|
||||
- Modification de la méthode `suspiciousDeletions` dans `WikiController.php` pour utiliser une approche plus efficace en mémoire
|
||||
- Utilisation de la méthode `extractJsonArrayByKey` au lieu de `json_decode(file_get_contents())` pour traiter le fichier JSON
|
||||
- Limitation du nombre d'éléments extraits à 50 avec le paramètre `$maxItems`
|
||||
- Ajout d'une limite pour arrêter le traitement après avoir trouvé 20 pages suspectes
|
||||
|
||||
## 7. Implémentation du suivi des pages "France" sans catégorie
|
||||
|
||||
### Fonctionnalités
|
||||
- Ajout de constantes pour l'URL des DeadendPages et le fichier de sortie dans `wiki_compare.py`
|
||||
- Création d'une fonction `fetch_deadend_pages` pour récupérer les pages commençant par "France" depuis la liste des DeadendPages
|
||||
- Création d'une fonction `suggest_categories` pour suggérer des catégories pour les pages sans catégorie
|
||||
- Modification de la fonction `main` pour traiter et sauvegarder les données des DeadendPages
|
||||
- Mise à jour du contrôleur pour charger et afficher les données des DeadendPages
|
||||
- Ajout de sections dans le template pour afficher les pages sans catégorie et les pages récemment catégorisées
|
||||
|
||||
### Avantages
|
||||
- Suivi des pages qui devraient avoir des catégories
|
||||
- Suggestion de catégories pour faciliter le travail des contributeurs
|
||||
- Suivi des progrès dans la catégorisation des pages
|
Binary file not shown.
|
@ -61,10 +61,12 @@ WIKI_BASE_URL_EN = "https://wiki.openstreetmap.org/wiki/Key:"
|
|||
WIKI_BASE_URL_FR = "https://wiki.openstreetmap.org/wiki/FR:Key:"
|
||||
WIKI_BASE_URL = "https://wiki.openstreetmap.org/wiki/"
|
||||
WIKI_CATEGORY_URL = "https://wiki.openstreetmap.org/wiki/Category:FR:Traductions_d%C3%A9synchronis%C3%A9es"
|
||||
WIKI_DEADEND_PAGES_URL = "https://wiki.openstreetmap.org/w/index.php?title=Special:DeadendPages&limit=500&offset=1000"
|
||||
TOP_KEYS_FILE = "top_keys.json"
|
||||
KEYS_WITHOUT_WIKI_FILE = "keys_without_wiki.json"
|
||||
WIKI_PAGES_CSV = "wiki_pages.csv"
|
||||
OUTDATED_PAGES_FILE = "outdated_pages.json"
|
||||
DEADEND_PAGES_FILE = "deadend_pages.json"
|
||||
STALENESS_HISTOGRAM_FILE = "staleness_histogram.png"
|
||||
# Number of wiki pages to examine
|
||||
NUM_WIKI_PAGES = 2
|
||||
|
@ -154,6 +156,161 @@ def fetch_desynchronized_pages():
|
|||
logger.error(f"Error fetching category page: {e}")
|
||||
return []
|
||||
|
||||
def suggest_categories(page_title, page_url):
|
||||
"""
|
||||
Suggest categories for an uncategorized page based on its title and content
|
||||
|
||||
Args:
|
||||
page_title (str): Title of the page
|
||||
page_url (str): URL of the page
|
||||
|
||||
Returns:
|
||||
list: List of suggested categories
|
||||
"""
|
||||
logger.info(f"Suggesting categories for page: {page_title}")
|
||||
|
||||
suggested_categories = []
|
||||
|
||||
# Common categories for French OSM wiki pages
|
||||
common_categories = [
|
||||
"Documentation OSM en français",
|
||||
"Cartographie",
|
||||
"Contributeurs",
|
||||
"Développeurs",
|
||||
"Éléments cartographiés",
|
||||
"Imports",
|
||||
"Logiciels",
|
||||
"Projets",
|
||||
"Rencontres",
|
||||
"Utilisateurs"
|
||||
]
|
||||
|
||||
# Add geography-related categories for pages about France
|
||||
if "France" in page_title:
|
||||
suggested_categories.append("France")
|
||||
|
||||
# Check for specific regions or departments
|
||||
regions = [
|
||||
"Auvergne-Rhône-Alpes", "Bourgogne-Franche-Comté", "Bretagne",
|
||||
"Centre-Val de Loire", "Corse", "Grand Est", "Hauts-de-France",
|
||||
"Île-de-France", "Normandie", "Nouvelle-Aquitaine",
|
||||
"Occitanie", "Pays de la Loire", "Provence-Alpes-Côte d'Azur"
|
||||
]
|
||||
|
||||
for region in regions:
|
||||
if region in page_title:
|
||||
suggested_categories.append(region)
|
||||
|
||||
# Try to fetch the page content to make better suggestions
|
||||
try:
|
||||
response = requests.get(page_url)
|
||||
response.raise_for_status()
|
||||
|
||||
soup = BeautifulSoup(response.text, 'html.parser')
|
||||
|
||||
# Get the main content
|
||||
content = soup.select_one('#mw-content-text')
|
||||
if content:
|
||||
text = content.get_text(separator=' ', strip=True).lower()
|
||||
|
||||
# Check for keywords related to common categories
|
||||
if any(keyword in text for keyword in ["carte", "cartographie", "mapper"]):
|
||||
suggested_categories.append("Cartographie")
|
||||
|
||||
if any(keyword in text for keyword in ["contribuer", "contributeur", "éditer"]):
|
||||
suggested_categories.append("Contributeurs")
|
||||
|
||||
if any(keyword in text for keyword in ["développeur", "programmer", "code", "api"]):
|
||||
suggested_categories.append("Développeurs")
|
||||
|
||||
if any(keyword in text for keyword in ["tag", "clé", "valeur", "élément", "nœud", "way", "relation"]):
|
||||
suggested_categories.append("Éléments cartographiés")
|
||||
|
||||
if any(keyword in text for keyword in ["import", "données", "dataset"]):
|
||||
suggested_categories.append("Imports")
|
||||
|
||||
if any(keyword in text for keyword in ["logiciel", "application", "outil"]):
|
||||
suggested_categories.append("Logiciels")
|
||||
|
||||
if any(keyword in text for keyword in ["projet", "initiative"]):
|
||||
suggested_categories.append("Projets")
|
||||
|
||||
if any(keyword in text for keyword in ["rencontre", "réunion", "événement", "conférence"]):
|
||||
suggested_categories.append("Rencontres")
|
||||
|
||||
if any(keyword in text for keyword in ["utiliser", "utilisateur", "usage"]):
|
||||
suggested_categories.append("Utilisateurs")
|
||||
|
||||
except requests.exceptions.RequestException as e:
|
||||
logger.warning(f"Error fetching page content for category suggestions: {e}")
|
||||
# If we can't fetch the content, suggest common categories based on title only
|
||||
if "projet" in page_title.lower():
|
||||
suggested_categories.append("Projets")
|
||||
elif "logiciel" in page_title.lower() or "application" in page_title.lower():
|
||||
suggested_categories.append("Logiciels")
|
||||
elif "rencontre" in page_title.lower() or "réunion" in page_title.lower():
|
||||
suggested_categories.append("Rencontres")
|
||||
|
||||
# Always suggest the general French documentation category
|
||||
suggested_categories.append("Documentation OSM en français")
|
||||
|
||||
# Remove duplicates while preserving order
|
||||
seen = set()
|
||||
unique_categories = []
|
||||
for cat in suggested_categories:
|
||||
if cat not in seen:
|
||||
seen.add(cat)
|
||||
unique_categories.append(cat)
|
||||
|
||||
logger.info(f"Suggested {len(unique_categories)} categories for {page_title}: {', '.join(unique_categories)}")
|
||||
return unique_categories
|
||||
|
||||
def fetch_deadend_pages():
|
||||
"""
|
||||
Fetch pages starting with "France" from the DeadendPages list
|
||||
|
||||
Returns:
|
||||
list: List of dictionaries containing page information
|
||||
"""
|
||||
logger.info(f"Fetching pages from DeadendPages list: {WIKI_DEADEND_PAGES_URL}")
|
||||
|
||||
try:
|
||||
response = requests.get(WIKI_DEADEND_PAGES_URL)
|
||||
response.raise_for_status()
|
||||
|
||||
soup = BeautifulSoup(response.text, 'html.parser')
|
||||
|
||||
# Find all links in the DeadendPages list
|
||||
page_links = []
|
||||
for link in soup.select('.mw-spcontent li a'):
|
||||
href = link.get('href', '')
|
||||
title = link.get_text(strip=True)
|
||||
|
||||
# Skip if it's not a wiki page or if it's a special page
|
||||
if not href.startswith('/wiki/') or 'Special:' in href:
|
||||
continue
|
||||
|
||||
# Filter pages that start with "France"
|
||||
if title.startswith('France'):
|
||||
# Get the full URL
|
||||
full_url = 'https://wiki.openstreetmap.org' + href
|
||||
|
||||
# Suggest categories for this page
|
||||
suggested_categories = suggest_categories(title, full_url)
|
||||
|
||||
page_links.append({
|
||||
'title': title,
|
||||
'url': full_url,
|
||||
'suggested_categories': suggested_categories
|
||||
})
|
||||
|
||||
logger.info(f"Found {len(page_links)} pages starting with 'France' in the DeadendPages list")
|
||||
return page_links
|
||||
|
||||
except requests.exceptions.RequestException as e:
|
||||
logger.error(f"Error fetching DeadendPages list: {e}")
|
||||
return []
|
||||
|
||||
def fetch_top_keys(limit=NUM_WIKI_PAGES):
|
||||
"""
|
||||
Fetch the most used OSM keys from TagInfo API
|
||||
|
@ -1365,10 +1522,11 @@ def main():
|
|||
3. Fetches and processes wiki pages for these keys
|
||||
4. Processes specific wiki pages listed in SPECIFIC_PAGES
|
||||
5. Processes pages from the FR:Traductions_désynchronisées category
|
||||
6. Calculates staleness scores for all pages
|
||||
7. Generates a histogram of staleness scores
|
||||
8. Saves the results to CSV and JSON files
|
||||
9. Prints a list of pages that need updating
|
||||
6. Processes pages starting with "France" from the DeadendPages list
|
||||
7. Calculates staleness scores for all pages
|
||||
8. Generates a histogram of staleness scores
|
||||
9. Saves the results to CSV and JSON files
|
||||
10. Prints a list of pages that need updating
|
||||
"""
|
||||
# Parse command-line arguments
|
||||
parser = argparse.ArgumentParser(description='Compare OpenStreetMap wiki pages in English and French.')
|
||||
|
@ -1405,6 +1563,62 @@ def main():
|
|||
else:
|
||||
logger.warning("No keys without wiki pages were fetched.")
|
||||
|
||||
# Fetch pages starting with "France" from the DeadendPages list
|
||||
deadend_pages = fetch_deadend_pages()
|
||||
|
||||
if deadend_pages:
|
||||
# Load existing deadend pages data to compare with history
|
||||
existing_data = load_json_data(DEADEND_PAGES_FILE)
|
||||
|
||||
# Initialize history if it doesn't exist
|
||||
if 'history' not in existing_data:
|
||||
existing_data['history'] = {}
|
||||
|
||||
# Get the most recent history entry
|
||||
sorted_timestamps = sorted(existing_data.get('history', {}).keys())
|
||||
previous_pages = []
|
||||
if sorted_timestamps:
|
||||
latest_timestamp = sorted_timestamps[-1]
|
||||
previous_pages = existing_data['history'][latest_timestamp].get('pages', [])
|
||||
|
||||
# Find pages that were in the previous list but are no longer in the current list
|
||||
previous_urls = [page['url'] for page in previous_pages]
|
||||
current_urls = [page['url'] for page in deadend_pages]
|
||||
|
||||
categorized_pages = []
|
||||
for url in previous_urls:
|
||||
if url not in current_urls:
|
||||
# Find the page in previous_pages
|
||||
for page in previous_pages:
|
||||
if page['url'] == url:
|
||||
# This page is no longer in the DeadendPages list, which means it has been categorized
|
||||
categorized_pages.append(page)
|
||||
break
|
||||
|
||||
# Create a timestamp for the current data
|
||||
current_timestamp = datetime.now().isoformat()
|
||||
|
||||
# Create the history entry
|
||||
history_entry = {
|
||||
'pages': deadend_pages,
|
||||
'categorized_pages': categorized_pages
|
||||
}
|
||||
|
||||
# Add the entry to history with timestamp as key
|
||||
existing_data['history'][current_timestamp] = history_entry
|
||||
|
||||
# Update the current data
|
||||
existing_data['pages'] = deadend_pages
|
||||
existing_data['categorized_pages'] = categorized_pages
|
||||
existing_data['last_updated'] = current_timestamp
|
||||
|
||||
# Save the updated data
|
||||
save_to_json(existing_data, DEADEND_PAGES_FILE)
|
||||
logger.info(f"Saved {len(deadend_pages)} deadend pages to {DEADEND_PAGES_FILE}")
|
||||
logger.info(f"Found {len(categorized_pages)} pages that have been categorized since the last run")
|
||||
else:
|
||||
logger.warning("No deadend pages were fetched.")
|
||||
|
||||
# Fetch wiki pages for each key
|
||||
wiki_pages = []
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue