Paginierte/gefilterte Listen: eigener Titel und Description (Page N, Kategorie)
Mit dem selbstreferenzierenden Canonical (07f74e3) zaehlten
/insights/news?page=2 und ?category=... als eigene Seiten mit dem
Titel von Seite 1 (Seobility: 3 doppelte Titel, 2x Duplicate Content).
VitecMetaHandler haengt jetzt ' - Page N' bzw. die Kategorie an Titel
und Description.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
8cdcd99eb3
commit
a9923e19e0
2 files changed
+25
-88
No files matched your search
@@ -290,6 +290,31 @@ class VitecMetaHandler extends MetaHandler
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Self-canonical pages need their own title/description, otherwise
|
||||||
|
// they count as duplicates of page 1 ("VITEC News and Articles" x3).
|
||||||
|
if ($keep !== []) {
|
||||||
|
$suffix = [];
|
||||||
|
if (isset($keep['category'])) {
|
||||||
|
$suffix[] = ucfirst(str_replace('-', ' ', $keep['category']));
|
||||||
|
}
|
||||||
|
if (isset($keep['page'])) {
|
||||||
|
$suffix[] = 'Page ' . (int)$keep['page'];
|
||||||
|
}
|
||||||
|
$suffixText = ' – ' . implode(' – ', $suffix);
|
||||||
|
$currentTitle = trim((string)($content['seo']['title'] ?? ''));
|
||||||
|
if ($currentTitle !== '') {
|
||||||
|
$content['seo']['title'] = preg_match('/^(.*?)(\s*\|\s*VITEC)$/i', $currentTitle, $m) === 1
|
||||||
|
? $m[1] . $suffixText . $m[2]
|
||||||
|
: $currentTitle . $suffixText;
|
||||||
|
}
|
||||||
|
foreach ($content['seo']['meta'] as $index => $entry) {
|
||||||
|
if (in_array($entry['name'] ?? '', ['description', 'twitter:description'], true)
|
||||||
|
|| ($entry['property'] ?? '') === 'og:description'
|
||||||
|
) {
|
||||||
|
$content['seo']['meta'][$index]['content'] = rtrim((string)$entry['content'], '. ') . $suffixText . '.';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $content;
|
return $content;
|
||||||
|
|||||||
@@ -1,88 +0,0 @@
|
|||||||
/**
|
|
||||||
* Live progress for the "Cache warming" row of the VITEC SSR module.
|
|
||||||
* Polls the vitec_ssr_health ajax route (GET /__health of the node):
|
|
||||||
* every 2 s while a warm run is active, every 15 s otherwise so runs
|
|
||||||
* started from the CLI or the context menu show up without a reload.
|
|
||||||
* Also refreshes the "Cache: N pages / M MB" line and disables the
|
|
||||||
* "Warm cache" button while a run is active.
|
|
||||||
*/
|
|
||||||
import AjaxRequest from '@typo3/core/ajax/ajax-request.js';
|
|
||||||
|
|
||||||
const cell = document.getElementById('ssr-warm-status');
|
|
||||||
if (cell) {
|
|
||||||
const wrap = document.getElementById('ssr-warm-progress');
|
|
||||||
const bar = document.getElementById('ssr-warm-bar');
|
|
||||||
const text = document.getElementById('ssr-warm-text');
|
|
||||||
const cacheLine = document.getElementById('ssr-cache-line');
|
|
||||||
const warmButton = document.getElementById('ssr-warm-button');
|
|
||||||
let timer = null;
|
|
||||||
|
|
||||||
const num = (v) => (typeof v === 'number' ? v : 0);
|
|
||||||
|
|
||||||
const render = (h) => {
|
|
||||||
const w = h.warming || {};
|
|
||||||
const queued = num(w.queued);
|
|
||||||
const handled = num(w.done) + num(w.skipped) + num(w.failed);
|
|
||||||
const pct = queued > 0 ? Math.min(100, Math.round((handled / queued) * 100)) : 0;
|
|
||||||
const failed = num(w.failed)
|
|
||||||
? '<span class="badge badge-danger">' + num(w.failed) + ' failed</span>'
|
|
||||||
: '0 failed';
|
|
||||||
|
|
||||||
if (w.running) {
|
|
||||||
wrap.style.display = '';
|
|
||||||
bar.style.width = pct + '%';
|
|
||||||
bar.textContent = pct + ' %';
|
|
||||||
text.innerHTML = '<span class="badge badge-info">running</span> '
|
|
||||||
+ handled + ' of ' + queued + ' pages ('
|
|
||||||
+ num(w.done) + ' rendered, ' + num(w.skipped) + ' still fresh, ' + failed + ')'
|
|
||||||
+ '<div class="form-text">Live - updates every 2 seconds.</div>';
|
|
||||||
} else if (w.startedAt) {
|
|
||||||
wrap.style.display = 'none';
|
|
||||||
const finished = w.finishedAt
|
|
||||||
? ' <span class="text-body-secondary">finished ' + new Date(w.finishedAt).toLocaleTimeString() + '</span>'
|
|
||||||
: '';
|
|
||||||
text.innerHTML = 'Last run: ' + num(w.done) + ' of ' + queued + ' pages rendered ('
|
|
||||||
+ num(w.skipped) + ' still fresh, ' + failed + ')'
|
|
||||||
+ (w.aborted ? ' <span class="badge badge-warning">aborted by restart</span>' : '')
|
|
||||||
+ finished;
|
|
||||||
} else {
|
|
||||||
wrap.style.display = 'none';
|
|
||||||
text.innerHTML = '<span class="text-body-secondary">no run since the server started</span>';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cacheLine && typeof h.cachedPages === 'number') {
|
|
||||||
const limit = parseInt(cacheLine.dataset.limit || '0', 10);
|
|
||||||
cacheLine.textContent = 'Cache: ' + h.cachedPages + ' pages · ' + num(h.cacheMB) + ' MB'
|
|
||||||
+ (limit > 0 ? ' of ' + limit + ' MB' : '');
|
|
||||||
}
|
|
||||||
if (warmButton) {
|
|
||||||
warmButton.disabled = !!w.running;
|
|
||||||
warmButton.classList.toggle('is-running', !!w.running);
|
|
||||||
}
|
|
||||||
return !!w.running;
|
|
||||||
};
|
|
||||||
|
|
||||||
const schedule = (ms) => {
|
|
||||||
clearTimeout(timer);
|
|
||||||
timer = setTimeout(tick, ms);
|
|
||||||
};
|
|
||||||
|
|
||||||
const tick = () => {
|
|
||||||
new AjaxRequest(TYPO3.settings.ajaxUrls.vitec_ssr_health).get()
|
|
||||||
.then((response) => response.resolve())
|
|
||||||
.then((data) => {
|
|
||||||
const running = data && data.health ? render(data.health) : false;
|
|
||||||
schedule(running ? 2000 : 15000);
|
|
||||||
})
|
|
||||||
.catch(() => schedule(15000));
|
|
||||||
};
|
|
||||||
|
|
||||||
schedule(cell.dataset.running === '1' ? 1000 : 15000);
|
|
||||||
document.addEventListener('visibilitychange', () => {
|
|
||||||
if (document.hidden) {
|
|
||||||
clearTimeout(timer);
|
|
||||||
} else {
|
|
||||||
tick();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
Reference in new issue
Block a user