initial state
This commit is contained in:
@@ -0,0 +1,328 @@
|
||||
/* Swiss Football Admin Styles */
|
||||
|
||||
/* Main admin sections */
|
||||
.swi-foot-admin-section {
|
||||
margin-top: 30px;
|
||||
padding-top: 20px;
|
||||
border-top: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.swi-foot-admin-section h2 {
|
||||
margin-bottom: 15px;
|
||||
color: #23282d;
|
||||
}
|
||||
|
||||
/* Teams grid layout */
|
||||
.swi-foot-teams-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
|
||||
gap: 15px;
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.team-card {
|
||||
padding: 15px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 6px;
|
||||
background: #f5f7fa;
|
||||
transition: all 0.3s ease;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.team-card:hover {
|
||||
background: #f0f0f0;
|
||||
border-color: #007cba;
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.team-card strong {
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
color: #23282d;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.team-card small {
|
||||
color: #666;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
/* Shortcodes help styling */
|
||||
.swi-foot-shortcodes-help {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.shortcode-group {
|
||||
margin-bottom: 15px;
|
||||
padding: 12px;
|
||||
background: #eef3f7;
|
||||
border-radius: 4px;
|
||||
border-left: 3px solid #007cba;
|
||||
}
|
||||
|
||||
.shortcode-group strong {
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
color: #111;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.shortcode-group code {
|
||||
display: block;
|
||||
margin: 4px 0;
|
||||
padding: 4px 6px;
|
||||
background: #fff;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 3px;
|
||||
font-size: 11px;
|
||||
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.shortcode-group code:hover {
|
||||
background: #f0f8ff;
|
||||
border-color: #007cba;
|
||||
transform: translateX(2px);
|
||||
}
|
||||
|
||||
.shortcode-group code:active {
|
||||
background: #e6f3ff;
|
||||
}
|
||||
|
||||
.shortcode-group ul {
|
||||
margin-top: 8px;
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
.shortcode-group li {
|
||||
margin: 3px 0;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.shortcode-group li code {
|
||||
display: inline;
|
||||
margin: 0;
|
||||
padding: 1px 3px;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.shortcode-group li code:hover {
|
||||
transform: none;
|
||||
}
|
||||
|
||||
/* Status indicators */
|
||||
#refresh-status,
|
||||
#cache-status,
|
||||
#connection-status {
|
||||
margin-left: 12px;
|
||||
font-style: italic;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
#refresh-status.success,
|
||||
#cache-status.success,
|
||||
#connection-status.success {
|
||||
color: #46b450;
|
||||
}
|
||||
|
||||
#refresh-status.error,
|
||||
#cache-status.error,
|
||||
#connection-status.error {
|
||||
color: #dc3232;
|
||||
}
|
||||
|
||||
/* Quick shortcode reference */
|
||||
.shortcode-quick-ref {
|
||||
background: #fff;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 6px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.shortcode-examples h4 {
|
||||
margin-bottom: 15px;
|
||||
color: #23282d;
|
||||
border-bottom: 1px solid #eee;
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
|
||||
.shortcode-examples p {
|
||||
margin-bottom: 15px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.shortcode-examples strong {
|
||||
color: #23282d;
|
||||
}
|
||||
|
||||
.shortcode-examples code {
|
||||
background: #f1f1f1;
|
||||
padding: 2px 4px;
|
||||
border-radius: 3px;
|
||||
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
/* Button enhancements */
|
||||
.button {
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.button:hover {
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.button:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.button:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
/* Settings form styling */
|
||||
.form-table th {
|
||||
padding-top: 15px;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.form-table td {
|
||||
padding-top: 12px;
|
||||
}
|
||||
|
||||
.form-table input[type="text"],
|
||||
.form-table input[type="url"],
|
||||
.form-table input[type="password"],
|
||||
.form-table input[type="number"] {
|
||||
transition: border-color 0.3s ease;
|
||||
}
|
||||
|
||||
.form-table input[type="text"]:focus,
|
||||
.form-table input[type="url"]:focus,
|
||||
.form-table input[type="password"]:focus,
|
||||
.form-table input[type="number"]:focus {
|
||||
border-color: #007cba;
|
||||
box-shadow: 0 0 0 1px #007cba;
|
||||
}
|
||||
|
||||
.form-table .description {
|
||||
color: #666;
|
||||
font-style: italic;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
/* Notice styling */
|
||||
.notice {
|
||||
margin: 15px 0;
|
||||
}
|
||||
|
||||
.notice.notice-success {
|
||||
border-left-color: #46b450;
|
||||
}
|
||||
|
||||
.notice.notice-error {
|
||||
border-left-color: #dc3232;
|
||||
}
|
||||
|
||||
/* Meta box styling in post editor */
|
||||
#swi-foot-shortcodes .inside {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
#swi-foot-shortcodes .shortcode-group {
|
||||
background: #f8f9fa;
|
||||
border: 1px solid #ddd;
|
||||
border-left: 3px solid #007cba;
|
||||
}
|
||||
|
||||
/* Loading states */
|
||||
.loading {
|
||||
opacity: 0.6;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.loading::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
margin: -10px 0 0 -10px;
|
||||
border: 2px solid #f3f3f3;
|
||||
border-top: 2px solid #007cba;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
/* Responsive design */
|
||||
@media (max-width: 768px) {
|
||||
.swi-foot-teams-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.shortcode-group code {
|
||||
font-size: 10px;
|
||||
padding: 3px 5px;
|
||||
}
|
||||
|
||||
.form-table th,
|
||||
.form-table td {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
.form-table th {
|
||||
font-weight: 600;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Dark mode support for WordPress admin */
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.team-card {
|
||||
background: #2c2c2c;
|
||||
border-color: #555;
|
||||
color: #f5f7fa;
|
||||
}
|
||||
|
||||
.team-card:hover {
|
||||
background: #383838;
|
||||
}
|
||||
.team-card strong {
|
||||
color: #F4F4F4;
|
||||
}
|
||||
|
||||
.shortcode-group {
|
||||
background: #2c2c2c;
|
||||
border-left-color: #00a0d2;
|
||||
color: #eef6fb;
|
||||
}
|
||||
|
||||
.shortcode-group code {
|
||||
background: #1e1e1e;
|
||||
border-color: #555;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.shortcode-group code:hover {
|
||||
background: #383838;
|
||||
border-color: #00a0d2;
|
||||
}
|
||||
|
||||
.shortcode-quick-ref {
|
||||
background: #2c2c2c;
|
||||
border-color: #555;
|
||||
color: #f3f7fb;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
// Swiss Football Admin JavaScript
|
||||
jQuery(document).ready(function($) {
|
||||
'use strict';
|
||||
|
||||
// Refresh teams functionality
|
||||
$('#refresh-teams').on('click', function() {
|
||||
var $button = $(this);
|
||||
var $status = $('#refresh-status');
|
||||
|
||||
$button.prop('disabled', true).text('Refreshing...');
|
||||
$status.text('').removeClass('success error');
|
||||
|
||||
fetch(swi_foot_ajax.rest_url.replace(/\/$/, '') + '/admin/refresh-teams', {
|
||||
method: 'POST',
|
||||
credentials: 'same-origin',
|
||||
headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': swi_foot_ajax.rest_nonce }
|
||||
}).then(function(resp) {
|
||||
return resp.json();
|
||||
}).then(function(response) {
|
||||
if (response && response.success) {
|
||||
$status.text('Teams refreshed successfully!').addClass('success');
|
||||
setTimeout(function() { location.reload(); }, 1500);
|
||||
} else {
|
||||
$status.text('Error: ' + (response.error || 'Unknown')).addClass('error');
|
||||
}
|
||||
}).catch(function() {
|
||||
$status.text('Network error occurred.').addClass('error');
|
||||
}).finally(function() {
|
||||
$button.prop('disabled', false).text('Refresh Teams List');
|
||||
});
|
||||
});
|
||||
|
||||
// Clear cache functionality
|
||||
$('#clear-cache').on('click', function() {
|
||||
var $button = $(this);
|
||||
var $status = $('#cache-status');
|
||||
|
||||
if (!confirm('Are you sure you want to clear the match data cache?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$button.prop('disabled', true);
|
||||
$status.text('Clearing cache...').removeClass('success error');
|
||||
|
||||
fetch(swi_foot_ajax.rest_url.replace(/\/$/, '') + '/admin/clear-cache', {
|
||||
method: 'POST',
|
||||
credentials: 'same-origin',
|
||||
headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': swi_foot_ajax.rest_nonce }
|
||||
}).then(function(resp) { return resp.json(); }).then(function(response) {
|
||||
if (response && response.success) {
|
||||
$status.text('Cache cleared successfully!').addClass('success');
|
||||
setTimeout(function() { location.reload(); }, 1000);
|
||||
} else {
|
||||
$status.text('Error clearing cache.').addClass('error');
|
||||
}
|
||||
}).catch(function() {
|
||||
$status.text('Error clearing cache.').addClass('error');
|
||||
}).finally(function() {
|
||||
$button.prop('disabled', false);
|
||||
});
|
||||
});
|
||||
|
||||
// Test API connection
|
||||
$('#test-connection').on('click', function() {
|
||||
var $button = $(this);
|
||||
var $status = $('#connection-status');
|
||||
|
||||
$button.prop('disabled', true).text('Testing...');
|
||||
$status.text('').removeClass('success error');
|
||||
|
||||
fetch(swi_foot_ajax.rest_url.replace(/\/$/, '') + '/admin/test-connection', {
|
||||
method: 'POST',
|
||||
credentials: 'same-origin',
|
||||
headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': swi_foot_ajax.rest_nonce }
|
||||
}).then(function(resp) { return resp.json(); }).then(function(response) {
|
||||
if (response && response.success) {
|
||||
$status.text('Connection successful!').addClass('success');
|
||||
} else {
|
||||
$status.text('Connection failed: ' + (response.error || 'Unknown')).addClass('error');
|
||||
}
|
||||
}).catch(function() {
|
||||
$status.text('Network error occurred.').addClass('error');
|
||||
}).finally(function() {
|
||||
$button.prop('disabled', false).text('Test API Connection');
|
||||
});
|
||||
});
|
||||
|
||||
// Auto-save settings notice
|
||||
$('form').on('submit', function() {
|
||||
$('<div class="notice notice-info is-dismissible"><p>Settings saved! The plugin will automatically refresh API tokens as needed.</p></div>')
|
||||
.insertAfter('.wrap h1');
|
||||
});
|
||||
|
||||
});
|
||||
@@ -0,0 +1,381 @@
|
||||
/* Swiss Football Blocks Styles - Minimal */
|
||||
|
||||
/* Editor block wrapper - identifies blocks in editor */
|
||||
.swi-foot-editor-block {
|
||||
background-color: #f5f5f5;
|
||||
border: 0.5px solid #d0d0d0;
|
||||
padding: 20px;
|
||||
margin: 20px 0;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
/* Block container styles */
|
||||
.swi-foot-standings,
|
||||
.swi-foot-schedule,
|
||||
.swi-foot-match-container {
|
||||
margin: 20px 0;
|
||||
padding: 20px;
|
||||
border: 0.5px solid #d0d0d0;
|
||||
border-radius: 4px;
|
||||
background-color: #f5f5f5;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
||||
}
|
||||
|
||||
/* Table styles */
|
||||
.swi-foot-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-top: 15px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.swi-foot-table th,
|
||||
.swi-foot-table td {
|
||||
padding: 10px 8px;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
.swi-foot-table th {
|
||||
background-color: #f8f9fa;
|
||||
font-weight: 600;
|
||||
color: #495057;
|
||||
border-bottom: 2px solid #dee2e6;
|
||||
font-size: 13px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.swi-foot-table tr.highlight {
|
||||
background-color: #fff3cd;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Event item styles */
|
||||
.swi-foot-event {
|
||||
padding: 16px;
|
||||
margin-bottom: 12px;
|
||||
border: 1px solid #e9ecef;
|
||||
border-left: 4px solid #007cba;
|
||||
border-radius: 6px;
|
||||
background-color: #fdfdfe;
|
||||
}
|
||||
|
||||
.event-date {
|
||||
font-weight: 600;
|
||||
color: #495057;
|
||||
font-size: 14px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.event-teams {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
margin: 8px 0;
|
||||
color: #212529;
|
||||
}
|
||||
|
||||
/* Match container - center aligned */
|
||||
.swi-foot-match-container {
|
||||
text-align: center;
|
||||
max-width: 450px;
|
||||
margin: 20px auto;
|
||||
}
|
||||
|
||||
.match-teams {
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
margin-bottom: 16px;
|
||||
color: #212529;
|
||||
}
|
||||
|
||||
.match-teams .vs {
|
||||
margin: 0 15px;
|
||||
color: #6c757d;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.match-date {
|
||||
font-size: 16px;
|
||||
color: #495057;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.match-score {
|
||||
font-size: 28px;
|
||||
font-weight: 800;
|
||||
color: #007cba;
|
||||
margin: 15px 0;
|
||||
padding: 12px 16px;
|
||||
border-radius: 8px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.match-status {
|
||||
font-size: 12px;
|
||||
padding: 6px 12px;
|
||||
border-radius: 15px;
|
||||
background-color: #e7f3ff;
|
||||
color: #007cba;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Inline shortcodes */
|
||||
.swi-foot-inline {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.swi-foot-inline.home-team,
|
||||
.swi-foot-inline.away-team {
|
||||
font-weight: 600;
|
||||
color: #007cba;
|
||||
}
|
||||
|
||||
.swi-foot-inline.score {
|
||||
font-weight: 700;
|
||||
color: #007cba;
|
||||
}
|
||||
|
||||
.swi-foot-inline.status {
|
||||
background: #007cba;
|
||||
color: white;
|
||||
padding: 2px 8px;
|
||||
border-radius: 4px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Error messages */
|
||||
.swi-foot-error {
|
||||
color: #721c24;
|
||||
padding: 12px 16px;
|
||||
background-color: #f8d7da;
|
||||
border: 1px solid #f5c6cb;
|
||||
border-radius: 6px;
|
||||
margin: 15px 0;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* Responsive design */
|
||||
@media (max-width: 768px) {
|
||||
.swi-foot-table {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.swi-foot-table th,
|
||||
.swi-foot-table td {
|
||||
padding: 6px 4px;
|
||||
}
|
||||
|
||||
.swi-foot-table th:nth-child(n+6),
|
||||
.swi-foot-table td:nth-child(n+6) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.swi-foot-table th:nth-child(n+4),
|
||||
.swi-foot-table td:nth-child(n+4) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* Enhanced Events Display Styles */
|
||||
.swi-foot-events {
|
||||
margin: 20px 0;
|
||||
padding: 20px;
|
||||
background-color: #fafafa;
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.swi-foot-events h3 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 20px;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.events-timeline {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.event-item {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 12px;
|
||||
padding: 12px;
|
||||
background-color: white;
|
||||
border-left: 4px solid #007cba;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
|
||||
transition: box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
.event-item:hover {
|
||||
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.event-minute {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 70px;
|
||||
padding: 8px 10px;
|
||||
background-color: #f0f6ff;
|
||||
border-radius: 4px;
|
||||
font-weight: 600;
|
||||
color: #007cba;
|
||||
font-size: 16px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.minute-time {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.event-main {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.event-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.event-type-info {
|
||||
flex: 1;
|
||||
min-width: 150px;
|
||||
}
|
||||
|
||||
.event-type {
|
||||
font-size: 15px;
|
||||
color: #222;
|
||||
}
|
||||
|
||||
.event-subtype {
|
||||
font-size: 13px;
|
||||
color: #666;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.team-logo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 50px;
|
||||
height: 50px;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.team-logo img {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
object-fit: contain;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
.team-name-fallback {
|
||||
display: inline-block;
|
||||
padding: 4px 8px;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
color: #666;
|
||||
background-color: #e8e8e8;
|
||||
border-radius: 3px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.event-details {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
font-size: 13px;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
.player-info {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 4px 8px;
|
||||
background-color: #f0f0f0;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.substitute-info {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 4px 8px;
|
||||
background-color: #fff3cd;
|
||||
border-radius: 3px;
|
||||
color: #856404;
|
||||
}
|
||||
|
||||
/* Team Logo Shortcodes */
|
||||
.swi-foot-team-logo {
|
||||
max-width: 120px;
|
||||
max-height: 120px;
|
||||
height: auto;
|
||||
object-fit: contain;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.swi-foot-team-logo.home-team {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.swi-foot-team-logo.away-team {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
/* Responsive Design for Events */
|
||||
@media (max-width: 768px) {
|
||||
.event-item {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.event-header {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.team-logo {
|
||||
order: -1;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.minute-time {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.event-minute {
|
||||
min-width: 60px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.event-details {
|
||||
font-size: 12px;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.swi-foot-team-logo {
|
||||
max-width: 80px;
|
||||
max-height: 80px;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,297 @@
|
||||
// Swiss Football Blocks Frontend JavaScript
|
||||
(function($) {
|
||||
'use strict';
|
||||
|
||||
// Team selector functionality for blocks
|
||||
window.swiFootSelectTeam = function(teamId, blockType) {
|
||||
if (!teamId) return;
|
||||
|
||||
// This could be enhanced to use AJAX for dynamic loading
|
||||
const url = new URL(window.location);
|
||||
url.searchParams.set('swi_foot_team_' + blockType, teamId);
|
||||
window.location.href = url.toString();
|
||||
};
|
||||
|
||||
// Auto-refresh functionality for events shortcodes
|
||||
$(document).ready(function() {
|
||||
initEventRefresh();
|
||||
setupShortcodeHelpers();
|
||||
});
|
||||
|
||||
/**
|
||||
* Initialize event refresh polling for all match event shortcodes on page
|
||||
*
|
||||
* Finds all `.swi-foot-events[data-match-id][data-refresh]` elements and sets up
|
||||
* automatic polling based on the configured refresh interval. Respects match status
|
||||
* (does not update UI until match starts, stops polling when match ends).
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
function initEventRefresh() {
|
||||
const $eventContainers = $('.swi-foot-events[data-match-id][data-refresh]');
|
||||
|
||||
if ($eventContainers.length === 0) return;
|
||||
|
||||
$eventContainers.each(function() {
|
||||
const $container = $(this);
|
||||
const matchId = $container.data('match-id');
|
||||
const interval = parseInt($container.data('refresh')) || 30;
|
||||
|
||||
if (!matchId) return;
|
||||
|
||||
// Initial check: determine if match has started and if it should refresh
|
||||
checkMatchStatusAndRefresh($container, matchId, interval);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize polling interval for match events
|
||||
*
|
||||
* Sets up a setInterval that periodically fetches the latest events for a match.
|
||||
* Automatically clears the interval when the match ends.
|
||||
*
|
||||
* @param {jQuery} $container - jQuery element for the event container
|
||||
* @param {string} matchId - ID of the match to fetch events for
|
||||
* @param {number} interval - Refresh interval in seconds (e.g., 30)
|
||||
* @returns {void}
|
||||
*/
|
||||
function checkMatchStatusAndRefresh($container, matchId, interval) {
|
||||
fetchMatchEvents($container, matchId);
|
||||
|
||||
// Set up polling - will stop automatically once match ends
|
||||
const pollInterval = setInterval(function() {
|
||||
const hasEnded = $container.data('match-ended');
|
||||
|
||||
// Stop polling if match has ended
|
||||
if (hasEnded) {
|
||||
clearInterval(pollInterval);
|
||||
return;
|
||||
}
|
||||
|
||||
fetchMatchEvents($container, matchId);
|
||||
}, interval * 1000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch latest match events from REST API
|
||||
*
|
||||
* Makes an authenticated request to `/wp-json/swi-foot/v1/events/{matchId}` to
|
||||
* retrieve the current list of match events. Updates container data with match
|
||||
* status (started/ended). Only updates the DOM if the match has started.
|
||||
*
|
||||
* @param {jQuery} $container - jQuery element for the event container
|
||||
* @param {string} matchId - Match ID to fetch events for
|
||||
* @returns {void}
|
||||
*/
|
||||
function fetchMatchEvents($container, matchId) {
|
||||
const restUrl = window.swiFootRest ? window.swiFootRest.rest_url : '/wp-json';
|
||||
const nonce = window.swiFootRest ? window.swiFootRest.rest_nonce : '';
|
||||
|
||||
// Get event order from data attribute
|
||||
const eventOrder = $container.data('event-order') || 'dynamic';
|
||||
|
||||
// Build URL with event_order parameter
|
||||
let url = restUrl + 'swi-foot/v1/events/' + encodeURIComponent(matchId);
|
||||
if (eventOrder && eventOrder !== 'dynamic') {
|
||||
url += '?event_order=' + encodeURIComponent(eventOrder);
|
||||
}
|
||||
|
||||
fetch(url, {
|
||||
method: 'GET',
|
||||
credentials: 'same-origin',
|
||||
headers: {
|
||||
'X-WP-Nonce': nonce
|
||||
}
|
||||
})
|
||||
.then(function(resp) {
|
||||
if (!resp.ok) throw new Error('Failed to fetch events');
|
||||
return resp.json();
|
||||
})
|
||||
.then(function(data) {
|
||||
if (!data) return;
|
||||
|
||||
// Track match status for polling control
|
||||
$container.data('match-started', data.hasMatchStarted);
|
||||
$container.data('match-ended', data.hasMatchEnded);
|
||||
|
||||
// Only update if match has started
|
||||
if (!data.hasMatchStarted) {
|
||||
return; // Don't update UI, match hasn't started yet
|
||||
}
|
||||
|
||||
// Update events list
|
||||
updateEventsList($container, data.events);
|
||||
})
|
||||
.catch(function(err) {
|
||||
console.error('Error fetching match events:', err);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the events timeline display with new events
|
||||
*
|
||||
* Renders an HTML list of match events from the API response, sorted newest first.
|
||||
* Handles empty event lists with a localized message. Updates the `.events-timeline`
|
||||
* element within the container.
|
||||
*
|
||||
* @param {jQuery} $container - jQuery element for the event container
|
||||
* @param {Array<Object>} events - Array of event objects with matchMinute, eventTypeName, playerName, teamName
|
||||
* @returns {void}
|
||||
*/
|
||||
function updateEventsList($container, events) {
|
||||
const $timeline = $container.find('.events-timeline');
|
||||
if ($timeline.length === 0) return;
|
||||
|
||||
// Build HTML for events
|
||||
let html = '';
|
||||
if (events && events.length > 0) {
|
||||
events.forEach(function(event) {
|
||||
html += '<div class="event-item">';
|
||||
html += '<div class="event-time">' + (event.matchMinute || '') + '\'</div>';
|
||||
html += '<div class="event-content">';
|
||||
html += '<div class="event-type">' + (event.eventTypeName || '') + '</div>';
|
||||
html += '<div class="event-details">';
|
||||
if (event.playerName) {
|
||||
html += '<span class="player">' + event.playerName + '</span>';
|
||||
}
|
||||
if (event.teamName) {
|
||||
html += '<span class="team">(' + event.teamName + ')</span>';
|
||||
}
|
||||
html += '</div></div></div>';
|
||||
});
|
||||
} else {
|
||||
html = '<p>' + (window.swiFootNoEvents || 'No events recorded yet.') + '</p>';
|
||||
}
|
||||
|
||||
$timeline.html(html);
|
||||
}
|
||||
|
||||
function setupShortcodeHelpers() {
|
||||
// Add copy functionality to shortcode examples in admin
|
||||
if ($('.shortcode-group code').length > 0) {
|
||||
$('.shortcode-group code').each(function() {
|
||||
const $code = $(this);
|
||||
$code.css('cursor', 'pointer');
|
||||
$code.attr('title', 'Click to copy shortcode');
|
||||
|
||||
$code.on('click', function() {
|
||||
const shortcode = $(this).text();
|
||||
copyToClipboard(shortcode);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function copyToClipboard(text) {
|
||||
if (navigator.clipboard && window.isSecureContext) {
|
||||
navigator.clipboard.writeText(text).then(function() {
|
||||
showNotification('Shortcode copied to clipboard!', 'success');
|
||||
}).catch(function() {
|
||||
fallbackCopyTextToClipboard(text);
|
||||
});
|
||||
} else {
|
||||
fallbackCopyTextToClipboard(text);
|
||||
}
|
||||
}
|
||||
|
||||
function fallbackCopyTextToClipboard(text) {
|
||||
const textArea = document.createElement('textarea');
|
||||
textArea.value = text;
|
||||
textArea.style.position = 'fixed';
|
||||
textArea.style.left = '-999999px';
|
||||
textArea.style.top = '-999999px';
|
||||
document.body.appendChild(textArea);
|
||||
textArea.focus();
|
||||
textArea.select();
|
||||
|
||||
try {
|
||||
document.execCommand('copy');
|
||||
showNotification('Shortcode copied to clipboard!', 'success');
|
||||
} catch (err) {
|
||||
showNotification('Failed to copy shortcode', 'error');
|
||||
}
|
||||
|
||||
document.body.removeChild(textArea);
|
||||
}
|
||||
|
||||
function showNotification(message, type) {
|
||||
const $notification = $('<div class="swi-foot-notification swi-foot-notification-' + type + '">' + message + '</div>');
|
||||
|
||||
$notification.css({
|
||||
position: 'fixed',
|
||||
top: '20px',
|
||||
right: '20px',
|
||||
padding: '10px 15px',
|
||||
borderRadius: '4px',
|
||||
color: '#fff',
|
||||
fontSize: '14px',
|
||||
zIndex: '10000',
|
||||
opacity: '0',
|
||||
transform: 'translateY(-20px)',
|
||||
transition: 'all 0.3s ease'
|
||||
});
|
||||
|
||||
if (type === 'success') {
|
||||
$notification.css('backgroundColor', '#28a745');
|
||||
} else {
|
||||
$notification.css('backgroundColor', '#dc3545');
|
||||
}
|
||||
|
||||
$('body').append($notification);
|
||||
|
||||
// Animate in
|
||||
setTimeout(function() {
|
||||
$notification.css({
|
||||
opacity: '1',
|
||||
transform: 'translateY(0)'
|
||||
});
|
||||
}, 100);
|
||||
|
||||
// Remove after 3 seconds
|
||||
setTimeout(function() {
|
||||
$notification.css({
|
||||
opacity: '0',
|
||||
transform: 'translateY(-20px)'
|
||||
});
|
||||
|
||||
setTimeout(function() {
|
||||
$notification.remove();
|
||||
}, 300);
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
// Initialize tooltips for match elements
|
||||
$(document).ready(function() {
|
||||
$('.swi-foot-match-container [title]').each(function() {
|
||||
const $element = $(this);
|
||||
$element.on('mouseenter', function() {
|
||||
// Simple tooltip implementation
|
||||
const title = $element.attr('title');
|
||||
if (title) {
|
||||
const $tooltip = $('<div class="swi-foot-tooltip">' + title + '</div>');
|
||||
$tooltip.css({
|
||||
position: 'absolute',
|
||||
background: '#333',
|
||||
color: '#fff',
|
||||
padding: '5px 8px',
|
||||
borderRadius: '4px',
|
||||
fontSize: '12px',
|
||||
zIndex: '1000',
|
||||
whiteSpace: 'nowrap'
|
||||
});
|
||||
|
||||
$('body').append($tooltip);
|
||||
|
||||
const rect = this.getBoundingClientRect();
|
||||
$tooltip.css({
|
||||
left: rect.left + (rect.width / 2) - ($tooltip.outerWidth() / 2),
|
||||
top: rect.top - $tooltip.outerHeight() - 5
|
||||
});
|
||||
}
|
||||
}).on('mouseleave', function() {
|
||||
$('.swi-foot-tooltip').remove();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
})(jQuery);
|
||||
@@ -0,0 +1,156 @@
|
||||
(function(wp){
|
||||
const { registerPlugin } = wp.plugins;
|
||||
const PluginDocumentSettingPanel = (wp && wp.editor && wp.editor.PluginDocumentSettingPanel) ? wp.editor.PluginDocumentSettingPanel : (wp && wp.editPost && wp.editPost.PluginDocumentSettingPanel) ? wp.editPost.PluginDocumentSettingPanel : null;
|
||||
const { withSelect, withDispatch } = wp.data;
|
||||
const { useState, useEffect } = wp.element;
|
||||
const { SelectControl, PanelRow, Spinner, Button } = wp.components;
|
||||
|
||||
function PostContextPanel(props) {
|
||||
const meta = (props.meta || {});
|
||||
const ctx = meta.swi_foot_context || {};
|
||||
|
||||
if (!PluginDocumentSettingPanel) return null;
|
||||
|
||||
const [teams, setTeams] = useState([]);
|
||||
const [matches, setMatches] = useState([]);
|
||||
const [loadingTeams, setLoadingTeams] = useState(false);
|
||||
const [loadingMatches, setLoadingMatches] = useState(false);
|
||||
|
||||
const restBaseRaw = (window.swi_foot_post_context && window.swi_foot_post_context.rest_url) || '/wp-json/swi-foot/v1';
|
||||
const restBase = restBaseRaw.replace(/\/$/, '');
|
||||
|
||||
useEffect(() => {
|
||||
setLoadingTeams(true);
|
||||
fetch(restBase + '/teams', { credentials: 'same-origin', headers: { 'X-WP-Nonce': window.swi_foot_post_context.rest_nonce } })
|
||||
.then(r => r.json()).then(data => {
|
||||
if (Array.isArray(data)) setTeams(data);
|
||||
}).catch(() => {}).finally(() => setLoadingTeams(false));
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const teamId = ctx.team_id || '';
|
||||
if (!teamId) {
|
||||
setMatches([]);
|
||||
return;
|
||||
}
|
||||
setLoadingMatches(true);
|
||||
fetch(restBase + '/matches?team_id=' + encodeURIComponent(teamId), { credentials: 'same-origin', headers: { 'X-WP-Nonce': window.swi_foot_post_context.rest_nonce } })
|
||||
.then(r => r.json()).then(data => {
|
||||
if (Array.isArray(data)) {
|
||||
setMatches(data);
|
||||
|
||||
// If no match selected yet, pick the next future match by date
|
||||
try {
|
||||
if (!ctx.match_id) {
|
||||
const now = Date.now();
|
||||
let next = null;
|
||||
data.forEach(m => {
|
||||
if (!m.matchDate) return;
|
||||
const t = Date.parse(m.matchDate);
|
||||
if (isNaN(t)) return;
|
||||
if (t > now) {
|
||||
if (!next || t < next.time) next = { time: t, id: m.matchId };
|
||||
}
|
||||
});
|
||||
if (next && next.id) {
|
||||
updateContext('match_id', next.id);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}).catch(() => {}).finally(() => setLoadingMatches(false));
|
||||
}, [ctx.team_id]);
|
||||
|
||||
function updateContext(key, val) {
|
||||
const newMeta = Object.assign({}, meta, { swi_foot_context: Object.assign({}, ctx, { [key]: val }) });
|
||||
props.editPost({ meta: newMeta });
|
||||
}
|
||||
|
||||
const teamOptions = [{ value: '', label: '— use post default —' }].concat(
|
||||
teams.map(t => ({ value: t.teamId, label: t.teamName + (t.teamLeagueName ? ' (' + t.teamLeagueName + ')' : '') }))
|
||||
);
|
||||
|
||||
const matchOptions = [{ value: '', label: '— use team/season default —' }].concat(
|
||||
matches.map(m => {
|
||||
var datePart = m.matchDate ? (m.matchDate.substr(0,10) + ' — ') : '';
|
||||
var opposing = m.matchId;
|
||||
var role = '';
|
||||
try {
|
||||
var selectedTeam = (ctx.team_id || '').toString();
|
||||
var teamAId = (m.teamAId || m.homeTeamId || '').toString();
|
||||
var teamBId = (m.teamBId || m.awayTeamId || '').toString();
|
||||
var teamAName = m.teamNameA || m.homeTeamName || '';
|
||||
var teamBName = m.teamNameB || m.awayTeamName || '';
|
||||
if (selectedTeam && selectedTeam === teamAId) {
|
||||
opposing = teamBName || teamAName || m.matchId;
|
||||
role = 'home';
|
||||
} else if (selectedTeam && selectedTeam === teamBId) {
|
||||
opposing = teamAName || teamBName || m.matchId;
|
||||
role = 'away';
|
||||
} else {
|
||||
opposing = teamBName || teamAName || m.teamName || m.matchId;
|
||||
}
|
||||
} catch (e) {
|
||||
opposing = m.teamNameB || m.teamNameA || m.awayTeamName || m.homeTeamName || m.teamName || m.matchId;
|
||||
}
|
||||
var label = datePart + opposing + (role ? (' (' + role + ')') : '');
|
||||
return { value: m.matchId, label: label };
|
||||
})
|
||||
);
|
||||
|
||||
return wp.element.createElement(
|
||||
PluginDocumentSettingPanel,
|
||||
{ title: 'Swiss Football Context', className: 'swi-foot-post-context-panel' },
|
||||
wp.element.createElement(PanelRow, null,
|
||||
wp.element.createElement('div', { style: { width: '100%' } },
|
||||
wp.element.createElement('label', null, 'Season'),
|
||||
wp.element.createElement('input', {
|
||||
type: 'number',
|
||||
value: ctx.season || window.swi_foot_post_context.default_season,
|
||||
onChange: function(e){ updateContext('season', e.target.value); },
|
||||
style: { width: '100%' }
|
||||
})
|
||||
)
|
||||
),
|
||||
wp.element.createElement(PanelRow, null,
|
||||
wp.element.createElement('div', { style: { width: '100%' } },
|
||||
wp.element.createElement('label', null, 'Team'),
|
||||
loadingTeams ? wp.element.createElement(Spinner, null) : wp.element.createElement(SelectControl, {
|
||||
value: ctx.team_id || '',
|
||||
options: teamOptions,
|
||||
onChange: function(v){ updateContext('team_id', v); },
|
||||
__next40pxDefaultSize: true,
|
||||
__nextHasNoMarginBottom: true
|
||||
})
|
||||
)
|
||||
),
|
||||
wp.element.createElement(PanelRow, null,
|
||||
wp.element.createElement('div', { style: { width: '100%' } },
|
||||
wp.element.createElement('label', null, 'Match'),
|
||||
loadingMatches ? wp.element.createElement(Spinner, null) : wp.element.createElement(SelectControl, {
|
||||
value: ctx.match_id || '',
|
||||
options: matchOptions,
|
||||
onChange: function(v){ updateContext('match_id', v); },
|
||||
__next40pxDefaultSize: true,
|
||||
__nextHasNoMarginBottom: true
|
||||
})
|
||||
)
|
||||
),
|
||||
wp.element.createElement(PanelRow, null,
|
||||
wp.element.createElement('div', { style: { width: '100%', color: '#666', fontSize: '12px', marginTop: '6px' } },
|
||||
ctx.match_id ? ('Match ID: ' + ctx.match_id) : ''
|
||||
)
|
||||
),
|
||||
wp.element.createElement(PanelRow, null,
|
||||
wp.element.createElement(Button, { isSecondary: true, onClick: function(){ props.editPost({ meta: Object.assign({}, meta, { swi_foot_context: {} }) }); } }, 'Clear Context')
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const Connected = withSelect( (select) => ({ meta: select('core/editor').getEditedPostAttribute('meta') || {} }) )( withDispatch( (dispatch) => ({ editPost: dispatch('core/editor').editPost }) )( PostContextPanel ) );
|
||||
|
||||
registerPlugin('swi-foot-post-context', { render: Connected });
|
||||
|
||||
})(window.wp);
|
||||
Reference in New Issue
Block a user