19 Commits

Author SHA1 Message Date
Dome b51ce9ab3f feat: add native code block feature with Prism highlighting and editor integration
- Implemented custom code block system for frontend and editors
- Integrated Prism.js for syntax highlighting (YAML + HTML support)
- Added copy-to-clipboard functionality with hover-based UI
- Introduced custom Gutenberg block for code input
- Added Classic Editor button for quick code insertion
- Implemented server-side rendering via the_content filter
- Added dedicated styling (code.css) with Dracula-inspired theme
- Added editor preview styling (editor.css) for visual consistency
- Ensured accessibility and keyboard support for copy button
- Optimized asset loading and versioning using filemtime()

This feature provides a lightweight, theme-native alternative to external code highlighting plugins.
2026-05-02 15:50:33 +02:00
Dome 3150f4da51 Merge branch 'main' of https://github.com/Domoel/Zeitfresser-Wordpress-Theme 2026-04-30 21:41:30 +02:00
Dome 1020442c06 refactor(inc, image-optimizer): restructure /inc architecture and standardize image optimizer module
- reorganized /inc directory structure for improved separation of concerns
  - grouped files into customizer, utilities, and tools
  - improved naming consistency across files (e.g. *-settings, template-tags, etc.)
  - simplified functions.php includes for better readability and maintainability

- refactored Customizer structure
  - extracted image optimizer settings from core-settings into dedicated module
  - consolidated settings, UI logic, and styles into a single feature file
  - improved naming consistency for hooks and functions

- standardized Image Optimizer admin tool
  - renamed "Performance Tools" to "Image Optimizer" across UI and hooks
  - updated admin page registration, callback names, and menu labels
  - aligned AJAX nonce naming for consistency and clarity

- preserved all existing logic and behavior (no functional changes)
- improved overall code organization and long-term maintainability

no breaking changes
2026-04-30 21:41:19 +02:00
Dome 81ed7efa1a enforce justify via css 2026-04-30 11:44:58 +02:00
Dome 84b2b85bf6 more robust toc scroll 2026-04-30 00:14:19 +02:00
Dome 3bcf9f47fb fixed toc progressbar 2026-04-29 23:23:31 +02:00
Dome 8ec3992945 fixec comment count alignment 2026-04-29 22:35:24 +02:00
Dome cac17ab39d fixed alignment and spacing, and highlight issues 2026-04-29 22:22:05 +02:00
Dome 042547c98f fixed fonts issue 2026-04-29 19:32:01 +02:00
Dome 33df5bbe2e refactor(css): restructure stylesheet, remove duplicates, and improve maintainability
* reorganized CSS into clear sections (Root, Base, Layout, Typography, Components, Utilities)
* removed redundant and duplicate rules (header, branding, search, media, etc.)
* consolidated repeated selectors into single sources of truth
* unified form styles (comments + CF7)
* cleaned up sidebar/widget styles and resolved conflicting rules
* fixed related posts grid issue (min-width: 0 for grid children)
* improved TOC structure and extracted shared calculations
* simplified social links and hover behavior
* removed unsafe global performance hint (will-change)
* added prefers-reduced-motion support for accessibility
* improved overall cascade predictability and reduced specificity conflicts

No visual changes intended (safe refactor).
2026-04-29 19:24:55 +02:00
Dome 798d3f9e1f Delete assets/community-badge.png 2026-04-27 09:24:12 +02:00
Dome d477c95917 Update readme.md 2026-04-27 09:23:44 +02:00
Dome 6bf38ae05d refactor(toc, customizer): improve TOC architecture and reorganize customizer settings
- Reorganized Customizer structure for improved clarity and maintainability
- Introduced consistent default values for all settings to ensure stable fallbacks
  when no user preferences are defined

- Refactored scroll-driven TOC implementation:
  - Optimized scroll handling using requestAnimationFrame
  - Reduced layout thrashing and unnecessary DOM reads
  - Improved heading detection logic (deterministic viewport trigger)
  - Enhanced positioning logic (responsive alignment + sidebar awareness)
  - Improved footer collision handling for more robust layout behavior

- Added optional IntersectionObserver-based TOC implementation:
  - Event-driven alternative to scroll-based approach
  - Currently not enabled by default
  - May not be supported long-term due to less deterministic behavior

- General cleanup and internal consistency improvements

chore: bump version to 2.4.0
2026-04-26 18:48:28 +02:00
Dome 36cad12351 Update style.css 2026-04-26 14:01:12 +02:00
Dome a9f1e799d4 refactor(toc): fix alignment and improve layout responsiveness
- add heuristic sidebar detection
- improve TOC positioning and width calculation
- enhance layout robustness across themes
2026-04-26 14:00:35 +02:00
Dome dbdaaff9f2 Update style.css 2026-04-26 08:44:15 +02:00
Dome 00341252a1 fix(toc): correct floating TOC alignment by resolving wrong sidebar reference
The floating TOC positioning logic assumed a correct sidebar reference
when calculating the horizontal offset. However, the selector used
(`aside, .sidebar, #secondary`) could match non-layout elements such as
hidden containers, mobile sidebars, or unrelated widgets.

This resulted in incorrect `getBoundingClientRect()` values and caused
the TOC to be positioned too far left or right, depending on which
element was matched.

Solution:
- Introduced a `getRealSidebar()` helper to dynamically detect the
  visually relevant sidebar element.
- Filters out non-visible or irrelevant elements based on size.
- Selects the right-most valid candidate, ensuring correct layout context.
- Uses the actual gap between content and sidebar to position the TOC
  symmetrically on the opposite side of the content.

Additional improvements:
- Cached sidebar lookup to avoid repeated DOM queries during scroll.
- Stabilized gap calculation with clamping to prevent layout drift.

Result:
The TOC now consistently aligns with the content column and mirrors
the sidebar spacing correctly across different layouts and breakpoints.
2026-04-26 07:27:52 +02:00
Dome 05de6f2028 fix(toc): restore correct floating TOC positioning after layout refactor
Fix incorrect TOC alignment caused by outdated DOM selector in toc.js.
The content reference element changed during layout refactor, breaking
position calculations for the floating TOC.

Updated contentColumn selector to use .main-wrapper/.container fallback,
ensuring correct left offset and responsive positioning.

Also adds a more robust fallback chain to prevent future regressions
when layout structure changes.
2026-04-26 04:43:04 +02:00
Dome 4408a738ec Update readme.md 2026-04-26 04:17:10 +02:00
30 changed files with 4089 additions and 303 deletions
Binary file not shown.

Before

Width:  |  Height:  |  Size: 277 KiB

+161
View File
@@ -0,0 +1,161 @@
/**
* Code block styles
*
* Frontend and editor preview styles for the Zeitfresser code block feature.
* The selectors are scoped to avoid leaking into unrelated blocks or plugins.
*/
/* -------------------------------------------------------------------------
Code block wrapper
------------------------------------------------------------------------- */
.ztfr-code {
position: relative;
background-color: var(--footer-color);
color: var(--light-color);
padding: 1.1rem 1.2rem;
margin: 1rem 0;
border-radius: 6px;
max-width: 100%;
overflow-x: auto;
font-family: var(--secondary-font);
font-size: 0.870rem;
line-height: 1.6;
font-weight: 400;
border: 1px solid rgba(248, 248, 242, 0.08);
}
/* -------------------------------------------------------------------------
Prism overrides
------------------------------------------------------------------------- */
.ztfr-code pre,
.ztfr-code code {
background: transparent;
color: inherit;
}
.ztfr-code pre[class*="language-"],
.ztfr-code code[class*="language-"] {
background: transparent !important;
padding: 0 !important;
margin: 0 !important;
white-space: pre-wrap !important;
word-break: break-word !important;
overflow-wrap: break-word !important;
}
.ztfr-code pre[class*="language-"] {
overflow-x: visible !important;
}
.ztfr-code pre code {
display: block;
padding: 1rem;
overflow-x: auto;
white-space: pre-wrap;
word-break: break-word;
overflow-wrap: break-word;
}
/* -------------------------------------------------------------------------
Copy button
------------------------------------------------------------------------- */
.ztfr-code__copy {
position: absolute;
top: 0.75rem;
right: 0.75rem;
background-color: var(--footer-color);
color: var(--light-color);
border: 1px solid rgba(248, 248, 242, 0.12);
border-radius: 4px;
font-size: 0.72rem;
line-height: 1;
padding: 0.45rem 0.65rem;
cursor: pointer;
opacity: 0;
pointer-events: none;
transition:
opacity 0.2s ease,
background-color 0.2s ease,
color 0.2s ease,
border-color 0.2s ease;
}
.ztfr-code:hover .ztfr-code__copy {
opacity: 1;
pointer-events: auto;
}
.ztfr-code__copy:hover {
background-color: var(--hover-color);
color: #f8f8f2;
}
.ztfr-code__copy:focus {
opacity: 1;
pointer-events: auto;
outline: 2px solid #bd93f9;
outline-offset: 2px;
}
/* -------------------------------------------------------------------------
Dracula token colors
------------------------------------------------------------------------- */
.ztfr-code .token.comment,
.ztfr-code .token.prolog,
.ztfr-code .token.doctype,
.ztfr-code .token.cdata {
color: #6272a4;
}
.ztfr-code .token.punctuation {
color: #f8f8f2;
}
.ztfr-code .token.tag,
.ztfr-code .token.constant,
.ztfr-code .token.symbol,
.ztfr-code .token.deleted {
color: #ff79c6;
}
.ztfr-code .token.attr-name,
.ztfr-code .token.property,
.ztfr-code .token.selector,
.ztfr-code .token.important,
.ztfr-code .token.atrule {
color: #8be9fd;
}
.ztfr-code .token.attr-value,
.ztfr-code .token.string,
.ztfr-code .token.char,
.ztfr-code .token.inserted {
color: #f1fa8c;
}
.ztfr-code .token.keyword,
.ztfr-code .token.boolean,
.ztfr-code .token.number {
color: #bd93f9;
}
.ztfr-code .token.operator,
.ztfr-code .token.entity,
.ztfr-code .token.url {
color: #f8f8f2;
}
.ztfr-code .token.function,
.ztfr-code .token.class-name {
color: #50fa7b;
}
.block-editor-block-list__layout .ztfr-code,
.editor-styles-wrapper .ztfr-code {
margin: 0;
}
+5 -6
View File
@@ -1,10 +1,9 @@
:root { :root {
--site-title-color: #f7f7fa; --light-color: #f7f7fa;
--primary-color: #f7f7fa; --dark-color: #1e1f29;
--secondary-color: #f7f7fa; --footer-color: #2f313d;
--light-color: #1e1f29; --hover-color: #bd93f9;
--grey-color: #f7f7fa;
--dark-color: #f7f7fa;
} }
body { body {
+116
View File
@@ -0,0 +1,116 @@
/**
* Editor styles
*
* Scoped styles for Gutenberg and Classic Editor.
*/
/* -------------------------------------------------------------------------
Editor base
------------------------------------------------------------------------- */
.editor-styles-wrapper,
.mce-content-body {
font-family: var(--secondary-font);
line-height: 1.7;
}
/* -------------------------------------------------------------------------
Shared code block preview
------------------------------------------------------------------------- */
.editor-styles-wrapper .ztfr-code,
.editor-styles-wrapper pre.language-yaml,
.mce-content-body pre.language-yaml {
position: relative;
background-color: #282a36;
color: #f8f8f2;
border: 1px solid rgba(248, 248, 242, 0.08);
border-radius: 6px;
padding: 1rem 1.2rem;
margin: 1rem 0;
max-width: 100%;
overflow-x: auto;
font-family: var(--secondary-font);
font-size: 0.95rem;
line-height: 1.7;
box-sizing: border-box;
}
/* -------------------------------------------------------------------------
Gutenberg preview
------------------------------------------------------------------------- */
.editor-styles-wrapper .ztfr-code pre,
.editor-styles-wrapper .ztfr-code code {
background: transparent;
color: inherit;
}
.editor-styles-wrapper .ztfr-code pre.language-yaml,
.editor-styles-wrapper .ztfr-code code.language-yaml {
background: transparent !important;
padding: 0 !important;
margin: 0 !important;
white-space: pre-wrap !important;
word-break: break-word !important;
overflow-wrap: break-word !important;
}
.editor-styles-wrapper .ztfr-code pre code {
display: block;
white-space: pre-wrap;
word-break: break-word;
overflow-wrap: break-word;
}
/* -------------------------------------------------------------------------
Classic Editor preview
------------------------------------------------------------------------- */
.mce-content-body pre.language-yaml {
white-space: pre-wrap;
word-break: break-word;
overflow-wrap: break-word;
}
.mce-content-body pre.language-yaml code.language-yaml {
display: block;
background: transparent;
color: inherit;
padding: 0;
margin: 0;
white-space: pre-wrap;
word-break: break-word;
overflow-wrap: break-word;
}
/* -------------------------------------------------------------------------
Editor label
------------------------------------------------------------------------- */
.editor-styles-wrapper .ztfr-code.is-editor-preview::before,
.mce-content-body pre.language-yaml::before {
content: "YAML";
display: inline-block;
margin-bottom: 0.75rem;
padding: 0.2rem 0.45rem;
border-radius: 4px;
background: #44475a;
color: #f8f8f2;
font-size: 0.72rem;
line-height: 1;
letter-spacing: 0.03em;
}
/* -------------------------------------------------------------------------
Lists
------------------------------------------------------------------------- */
.editor-styles-wrapper ul,
.editor-styles-wrapper ol,
.mce-content-body ul,
.mce-content-body ol {
margin-left: 0;
padding-left: 1.25em;
list-style-position: outside;
}
+2 -2
View File
@@ -59,8 +59,8 @@
========================= */ ========================= */
:root { :root {
--primary-font: 'Oswald', var(--zeitfresser-heading-fallback); --primary-font: 'Oswald', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
--secondary-font: 'Roboto', var(--zeitfresser-body-fallback); --secondary-font: 'Roboto', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
--site-identity-font-size: 40px; --site-identity-font-size: 40px;
+94
View File
@@ -0,0 +1,94 @@
/**
* Code Block UI Enhancements
*
* Handles:
* - Copy button
* - Prism highlight trigger
*/
(function () {
'use strict';
function copyText(text, onSuccess, onError) {
if (!text) {
return;
}
if (navigator.clipboard && window.isSecureContext) {
navigator.clipboard.writeText(text).then(onSuccess).catch(onError);
return;
}
const textarea = document.createElement('textarea');
textarea.value = text;
textarea.setAttribute('readonly', 'readonly');
textarea.style.position = 'fixed';
textarea.style.opacity = '0';
textarea.style.pointerEvents = 'none';
document.body.appendChild(textarea);
textarea.focus();
textarea.select();
try {
document.execCommand('copy');
onSuccess();
} catch (error) {
onError();
}
document.body.removeChild(textarea);
}
function initCodeUI() {
const wrappers = document.querySelectorAll('.ztfr-code');
if (!wrappers.length) {
return;
}
wrappers.forEach(function (wrapper) {
const code = wrapper.querySelector('code');
if (!code) {
return;
}
if (!wrapper.querySelector('.ztfr-code__copy')) {
const button = document.createElement('button');
button.className = 'ztfr-code__copy';
button.type = 'button';
button.setAttribute('aria-label', 'Copy code');
button.textContent = 'Copy';
button.addEventListener('click', function () {
const text = code.textContent;
copyText(
text,
function () {
button.textContent = 'Copied';
window.setTimeout(function () {
button.textContent = 'Copy';
}, 2000);
},
function () {
button.textContent = 'Error';
window.setTimeout(function () {
button.textContent = 'Copy';
}, 2000);
}
);
});
wrapper.appendChild(button);
}
});
if (typeof Prism !== 'undefined') {
Prism.highlightAll();
}
}
document.addEventListener('DOMContentLoaded', initCodeUI);
})();
+113
View File
@@ -0,0 +1,113 @@
(function () {
'use strict';
/**
* Gutenberg block
*/
if (
window.wp &&
window.wp.blocks &&
window.wp.element &&
window.wp.i18n &&
window.wp.components &&
window.wp.blockEditor
) {
const blocks = window.wp.blocks;
const element = window.wp.element;
const i18n = window.wp.i18n;
const blockEditor = window.wp.blockEditor;
const el = element.createElement;
const __ = i18n.__;
const PlainText = blockEditor.PlainText;
blocks.registerBlockType('ztfr/code-block', {
title: __('Code', 'zeitfresser'),
icon: 'editor-code',
category: 'formatting',
description: __('Insert a styled YAML code block.', 'zeitfresser'),
supports: {
html: false
},
attributes: {
content: {
type: 'string',
default: ''
}
},
edit: function (props) {
const content = props.attributes.content || '';
return el(
'div',
{
className: 'ztfr-code is-editor-preview',
'data-language': 'yaml'
},
el(
'pre',
{ className: 'language-yaml' },
el(PlainText, {
tagName: 'code',
className: 'language-yaml',
value: content,
placeholder: __('Write or paste YAML code here…', 'zeitfresser'),
onChange: function (value) {
props.setAttributes({ content: value });
}
})
)
);
},
save: function (props) {
const content = props.attributes.content || '';
return el(
'pre',
{ className: 'language-yaml' },
el(
'code',
{ className: 'language-yaml' },
content
)
);
}
});
}
/**
* Classic Editor TinyMCE button
*/
if (window.tinymce && window.tinymce.PluginManager) {
function escapeHtml(text) {
return String(text)
.replace(/&/g, '&')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#039;');
}
window.tinymce.PluginManager.add('ztfr_code_block', function (editor) {
function insertCodeBlock() {
const selectedText = editor.selection.getContent({ format: 'text' });
const code = selectedText || 'your_key: your_value';
const safeCode = escapeHtml(code);
editor.insertContent(
'<pre class="language-yaml"><code class="language-yaml">' +
safeCode +
'</code></pre><p></p>'
);
}
editor.addButton('ztfr_code_block', {
text: 'Code',
icon: false,
onclick: insertCodeBlock
});
});
}
})();
File diff suppressed because one or more lines are too long
@@ -0,0 +1,325 @@
/**
* Floating TOC (Scroll-Driven Implementation)
*
* This implementation uses a scroll-based approach combined with
* requestAnimationFrame to determine the currently active heading.
* The active section is calculated based on a fixed viewport trigger
* (offset from the top), ensuring predictable and stable behavior.
*
* Design Goals:
* - Deterministic highlighting (no competing states)
* - Visual stability (no flickering or race conditions)
* - Theme compatibility (no reliance on experimental APIs)
* - Maintainable and debuggable logic
*
* Characteristics:
* - Uses a cached list of headings for efficient iteration
* - Throttles scroll handling via requestAnimationFrame
* - Minimizes DOM writes and layout recalculations
* - Separates layout (positioning) from state (active link)
*
* Trade-offs:
* - Runs continuously during scroll (minor CPU overhead)
* - Relies on getBoundingClientRect for visibility detection
* - Less "event-driven" than IntersectionObserver-based approaches
*
* Notes:
* This approach was chosen over IntersectionObserver to guarantee
* consistent ordering, eliminate flickering edge cases, and provide
* fully deterministic behavior across all layouts and browsers.
*/
document.addEventListener('DOMContentLoaded', function () {
var toc = document.getElementById('zeitfresser-floating-toc');
var title = document.querySelector(
'.zeitfresser-article-heading .page-title, ' +
'.zeitfresser-article-heading .entry-title, ' +
'.entry-header .entry-title'
);
var progressBar = document.getElementById('zeitfresser-floating-toc-progress');
var nav = toc ? toc.querySelector('.zeitfresser-floating-toc__nav') : null;
if (!toc || !title) {
return;
}
var links = Array.prototype.slice.call(toc.querySelectorAll('a[data-target]'));
var desktopQuery = window.matchMedia('(min-width: 1500px)');
var stickyTop = 100;
var headingOffset = 88;
var ticking = false;
var tocBottomOffset = null;
var cachedSidebar = null;
var headings = getHeadings();
function isDesktop() {
return desktopQuery.matches;
}
function getTarget(link) {
var id = link.getAttribute('data-target');
return id ? document.getElementById(id) : null;
}
function getHeadings() {
return links
.map(function (link) {
return { link: link, target: getTarget(link) };
})
.filter(function (item) {
return !!item.target;
});
}
function getArticleElement() {
return document.querySelector(
'.single-post .post-content article, ' +
'.single-post .post-content, ' +
'article.post, article, ' +
'.entry-content'
);
}
function getTocBottomOffset() {
if (tocBottomOffset !== null) return tocBottomOffset;
var value = getComputedStyle(document.documentElement)
.getPropertyValue('--toc-bottom-offset')
.trim();
tocBottomOffset = parseInt(value, 10) || 12;
return tocBottomOffset;
}
function getRealSidebar() {
if (cachedSidebar) return cachedSidebar;
var candidates = Array.prototype.slice.call(
document.querySelectorAll('aside, .sidebar, #secondary')
);
cachedSidebar = candidates
.filter(function (el) {
var rect = el.getBoundingClientRect();
return rect.width > 200 && rect.height > 200;
})
.sort(function (a, b) {
var rectA = a.getBoundingClientRect();
var rectB = b.getBoundingClientRect();
return rectB.left - rectA.left;
})[0] || null;
return cachedSidebar;
}
function syncPosition() {
if (!isDesktop()) {
document.documentElement.style.setProperty('--zeitfresser-toc-top', stickyTop + 'px');
document.documentElement.style.setProperty('--zeitfresser-toc-left', '24px');
document.documentElement.style.setProperty('--zeitfresser-toc-width', '220px');
return;
}
var scrollTop = window.scrollY || window.pageYOffset || 0;
var titleRect = title.getBoundingClientRect();
// 🔥 bessere Content-Erkennung
var contentColumn =
document.querySelector('.inside-page .main-wrapper > section') ||
document.querySelector('#primary') ||
document.querySelector('.content-area') ||
title;
if (!contentColumn) return;
var sidebar = getRealSidebar();
var contentRect = contentColumn.getBoundingClientRect();
var sidebarRect = sidebar ? sidebar.getBoundingClientRect() : null;
var gap = 48;
if (sidebarRect) {
gap = Math.abs(sidebarRect.left - contentRect.right);
gap = Math.max(32, Math.min(gap, 120));
}
var maxWidth = Math.max(Math.round(contentRect.left - gap - 24), 180);
var tocWidth = Math.max(220, Math.min(260, maxWidth));
var tocLeft = Math.max(
24,
Math.round(contentRect.left - gap - tocWidth)
);
var tocTop = Math.max(
stickyTop,
Math.round(titleRect.top + scrollTop + 14)
);
document.documentElement.style.setProperty('--zeitfresser-toc-top', tocTop + 'px');
document.documentElement.style.setProperty('--zeitfresser-toc-left', tocLeft + 'px');
document.documentElement.style.setProperty('--zeitfresser-toc-width', tocWidth + 'px');
}
function handleFooterCollision() {
if (!isDesktop()) {
toc.style.transform = '';
return;
}
var article = getArticleElement();
if (!article) {
toc.style.transform = '';
return;
}
toc.style.transform = '';
var scrollTop = window.scrollY || window.pageYOffset;
var articleRect = article.getBoundingClientRect();
var articleBottom = articleRect.top + scrollTop + articleRect.height;
var tocRect = toc.getBoundingClientRect();
var tocTop = tocRect.top + scrollTop;
var tocHeight = tocRect.height;
var tocBottom = tocTop + tocHeight;
var offset = getTocBottomOffset();
var maxBottom = articleBottom - offset;
var overflow = Math.ceil(tocBottom - maxBottom);
if (overflow > 0) {
toc.style.transform = 'translateY(-' + overflow + 'px)';
}
}
function setActiveLink(id) {
links.forEach(function (link) {
var active = link.getAttribute('data-target') === id;
link.classList.toggle('is-active', active);
if (active) {
link.setAttribute('aria-current', 'true');
} else {
link.removeAttribute('aria-current');
}
});
}
function updateProgress() {
if (!progressBar) return;
var article = getArticleElement();
if (!article) {
progressBar.style.width = '0%';
return;
}
var rect = article.getBoundingClientRect();
var total = Math.max(
Math.max(article.scrollHeight, article.offsetHeight) - window.innerHeight,
1
);
var progress = Math.min(
Math.max((-rect.top / total) * 100, 0),
100
);
progressBar.style.width = progress + '%';
}
function updateActiveHeading() {
if (!headings.length) return;
var currentId = headings[0].target.id;
var triggerY = headingOffset + 24;
for (var i = 0; i < headings.length; i++) {
var rectTop = headings[i].target.getBoundingClientRect().top;
if (rectTop <= triggerY) {
currentId = headings[i].target.id;
} else {
break;
}
}
setActiveLink(currentId);
}
function onViewportChange() {
if (ticking) return;
ticking = true;
window.requestAnimationFrame(function () {
syncPosition();
handleFooterCollision();
updateProgress();
updateActiveHeading();
ticking = false;
});
}
links.forEach(function (link) {
link.addEventListener('click', function (event) {
var target = getTarget(link);
if (!target) return;
event.preventDefault();
var top =
target.getBoundingClientRect().top +
window.scrollY -
headingOffset;
window.scrollTo({
top: top,
behavior: 'smooth'
});
setActiveLink(target.id);
});
});
if (nav) {
nav.addEventListener(
'wheel',
function (event) {
var canScroll = nav.scrollHeight > nav.clientHeight;
if (!canScroll) return;
var atTop = nav.scrollTop <= 0;
var atBottom =
Math.ceil(nav.scrollTop + nav.clientHeight) >= nav.scrollHeight;
if (
(event.deltaY < 0 && !atTop) ||
(event.deltaY > 0 && !atBottom)
) {
event.preventDefault();
nav.scrollTop += event.deltaY;
}
},
{ passive: false }
);
}
// Initial run
syncPosition();
handleFooterCollision();
updateProgress();
updateActiveHeading();
requestAnimationFrame(function () {
toc.classList.add('is-visible');
});
window.addEventListener('scroll', onViewportChange, { passive: true });
window.addEventListener('resize', onViewportChange, { passive: true });
window.addEventListener('load', function () {
syncPosition();
});
});
+299
View File
@@ -0,0 +1,299 @@
/**
* Floating TOC (IntersectionObserver Version)
*
* This implementation relies on the IntersectionObserver API to detect which
* headings are currently visible in the viewport. It is event-driven and
* more efficient, as updates occur only when visibility changes.
*
* Pros:
* - Lower CPU usage (no continuous polling)
* - Native browser optimization
*
* Cons:
* - Can produce multiple competing active states
* - May require tuning to avoid flickering or reordering
*/
document.addEventListener('DOMContentLoaded', function () {
var toc = document.getElementById('zeitfresser-floating-toc');
var title = document.querySelector('.zeitfresser-article-heading .page-title, .zeitfresser-article-heading .entry-title, .entry-header .entry-title');
var progressBar = document.getElementById('zeitfresser-floating-toc-progress');
var nav = toc ? toc.querySelector('.zeitfresser-floating-toc__nav') : null;
if (!toc || !title) {
return;
}
var links = Array.prototype.slice.call(toc.querySelectorAll('a[data-target]'));
var desktopQuery = window.matchMedia('(min-width: 1500px)');
var stickyTop = 100;
var headingOffset = 88;
var ticking = false;
var cachedSidebar = null;
var tocBottomOffset = null;
function isDesktop() {
return desktopQuery.matches;
}
function getTarget(link) {
var id = link.getAttribute('data-target');
return id ? document.getElementById(id) : null;
}
function getHeadings() {
return links.map(function (link) {
return {
link: link,
target: getTarget(link)
};
}).filter(function (item) {
return !!item.target;
});
}
function getArticleElement() {
return document.querySelector(
'.single-post .post-content article, ' +
'.single-post .post-content, ' +
'article.post, article, ' +
'.entry-content'
);
}
function getTocBottomOffset() {
if (tocBottomOffset !== null) return tocBottomOffset;
var value = getComputedStyle(document.documentElement)
.getPropertyValue('--toc-bottom-offset')
.trim();
tocBottomOffset = parseInt(value, 10) || 12;
return tocBottomOffset;
}
function getRealSidebar() {
if (cachedSidebar) return cachedSidebar;
var candidates = Array.prototype.slice.call(
document.querySelectorAll('aside, .sidebar, #secondary')
);
cachedSidebar = candidates
.filter(function (el) {
var rect = el.getBoundingClientRect();
return rect.width > 200 && rect.height > 200;
})
.sort(function (a, b) {
return b.getBoundingClientRect().left - a.getBoundingClientRect().left;
})[0] || null;
return cachedSidebar;
}
function syncPosition() {
if (!isDesktop()) {
document.documentElement.style.setProperty('--zeitfresser-toc-top', stickyTop + 'px');
document.documentElement.style.setProperty('--zeitfresser-toc-left', '24px');
document.documentElement.style.setProperty('--zeitfresser-toc-width', '220px');
return;
}
var scrollTop = window.scrollY || window.pageYOffset || 0;
var titleRect = title.getBoundingClientRect();
// 🔥 bessere Content-Erkennung
var contentColumn =
document.querySelector('.inside-page .main-wrapper > section') ||
document.querySelector('#primary') ||
document.querySelector('.content-area') ||
title;
if (!contentColumn) return;
var sidebar = getRealSidebar();
var contentRect = contentColumn.getBoundingClientRect();
var sidebarRect = sidebar ? sidebar.getBoundingClientRect() : null;
var gap = 48;
if (sidebarRect) {
gap = Math.abs(sidebarRect.left - contentRect.right);
gap = Math.max(32, Math.min(gap, 120));
}
// Toc Content Breite
var maxWidth = Math.max(Math.round(contentRect.left - gap - 24), 180);
var tocWidth = Math.max(220, Math.min(260, maxWidth));
var tocLeft = Math.max(
24,
Math.round(contentRect.left - gap - tocWidth)
);
var tocTop = Math.max(
stickyTop,
Math.round(titleRect.top + scrollTop + 14)
);
document.documentElement.style.setProperty('--zeitfresser-toc-top', tocTop + 'px');
document.documentElement.style.setProperty('--zeitfresser-toc-left', tocLeft + 'px');
document.documentElement.style.setProperty('--zeitfresser-toc-width', tocWidth + 'px');
}
function handleFooterCollision() {
if (!isDesktop()) {
toc.style.transform = '';
return;
}
var article = getArticleElement();
if (!article) {
toc.style.transform = '';
return;
}
toc.style.transform = '';
var scrollTop = window.scrollY || window.pageYOffset;
var articleRect = article.getBoundingClientRect();
var articleBottom = articleRect.top + scrollTop + articleRect.height;
var tocRect = toc.getBoundingClientRect();
var tocTop = tocRect.top + scrollTop;
var tocHeight = tocRect.height;
var tocBottom = tocTop + tocHeight;
var offset = getTocBottomOffset();
var maxBottom = articleBottom - offset;
var overflow = Math.ceil(tocBottom - maxBottom);
if (overflow > 0) {
toc.style.transform = 'translateY(-' + overflow + 'px)';
}
}
function setActiveLink(id) {
links.forEach(function (link) {
var active = link.getAttribute('data-target') === id;
link.classList.toggle('is-active', active);
if (active) {
link.setAttribute('aria-current', 'true');
} else {
link.removeAttribute('aria-current');
}
});
}
function updateProgress() {
if (!progressBar) return;
var article = getArticleElement();
if (!article) {
progressBar.style.width = '0%';
return;
}
var rect = article.getBoundingClientRect();
var total = Math.max(article.offsetHeight - window.innerHeight, 1);
var progress = Math.min(Math.max((-rect.top / total) * 100, 0), 100);
progressBar.style.width = progress + '%';
}
function onViewportChange() {
if (ticking) return;
ticking = true;
window.requestAnimationFrame(function () {
syncPosition();
handleFooterCollision();
updateProgress();
ticking = false;
});
}
// 🔥 NEW: IntersectionObserver for active headings
let currentActiveId = null;
function initIntersectionObserver() {
const headings = getHeadings();
if (!headings.length) return;
const observer = new IntersectionObserver(function (entries) {
entries.forEach(function (entry) {
if (entry.isIntersecting) {
const id = entry.target.id;
if (id !== currentActiveId) {
currentActiveId = id;
setActiveLink(id);
}
}
});
}, {
root: null,
rootMargin: `-${headingOffset}px 0px -70% 0px`,
threshold: 0
});
headings.forEach(function (item) {
if (item.target) {
observer.observe(item.target);
}
});
}
links.forEach(function (link) {
link.addEventListener('click', function (event) {
var target = getTarget(link);
if (!target) return;
event.preventDefault();
var top = target.getBoundingClientRect().top + window.scrollY - headingOffset;
window.scrollTo({
top: top,
behavior: 'smooth'
});
});
});
if (nav) {
nav.addEventListener('wheel', function (event) {
var canScroll = nav.scrollHeight > nav.clientHeight;
if (!canScroll) return;
var atTop = nav.scrollTop <= 0;
var atBottom = Math.ceil(nav.scrollTop + nav.clientHeight) >= nav.scrollHeight;
if ((event.deltaY < 0 && !atTop) || (event.deltaY > 0 && !atBottom)) {
event.preventDefault();
nav.scrollTop += event.deltaY;
}
}, { passive: false });
}
// Initial run
syncPosition();
handleFooterCollision();
updateProgress();
initIntersectionObserver();
requestAnimationFrame(function () {
toc.classList.add('is-visible');
});
window.addEventListener('scroll', onViewportChange, { passive: true });
window.addEventListener('resize', onViewportChange, { passive: true });
});
+134 -52
View File
@@ -1,6 +1,41 @@
/**
* Floating TOC (Scroll-Driven Implementation)
*
* This implementation uses a scroll-based approach combined with
* requestAnimationFrame to determine the currently active heading.
* The active section is calculated based on a fixed viewport trigger
* (offset from the top), ensuring predictable and stable behavior.
*
* Design Goals:
* - Deterministic highlighting (no competing states)
* - Visual stability (no flickering or race conditions)
* - Theme compatibility (no reliance on experimental APIs)
* - Maintainable and debuggable logic
*
* Characteristics:
* - Uses a cached list of headings for efficient iteration
* - Throttles scroll handling via requestAnimationFrame
* - Minimizes DOM writes and layout recalculations
* - Separates layout (positioning) from state (active link)
*
* Trade-offs:
* - Runs continuously during scroll (minor CPU overhead)
* - Relies on getBoundingClientRect for visibility detection
* - Less "event-driven" than IntersectionObserver-based approaches
*
* Notes:
* This approach was chosen over IntersectionObserver to guarantee
* consistent ordering, eliminate flickering edge cases, and provide
* fully deterministic behavior across all layouts and browsers.
*/
document.addEventListener('DOMContentLoaded', function () { document.addEventListener('DOMContentLoaded', function () {
var toc = document.getElementById('zeitfresser-floating-toc'); var toc = document.getElementById('zeitfresser-floating-toc');
var title = document.querySelector('.zeitfresser-article-heading .page-title, .zeitfresser-article-heading .entry-title, .entry-header .entry-title'); var title = document.querySelector(
'.zeitfresser-article-heading .page-title, ' +
'.zeitfresser-article-heading .entry-title, ' +
'.entry-header .entry-title'
);
var progressBar = document.getElementById('zeitfresser-floating-toc-progress'); var progressBar = document.getElementById('zeitfresser-floating-toc-progress');
var nav = toc ? toc.querySelector('.zeitfresser-floating-toc__nav') : null; var nav = toc ? toc.querySelector('.zeitfresser-floating-toc__nav') : null;
@@ -13,8 +48,9 @@ document.addEventListener('DOMContentLoaded', function () {
var stickyTop = 100; var stickyTop = 100;
var headingOffset = 88; var headingOffset = 88;
var ticking = false; var ticking = false;
var tocBottomOffset = null; var tocBottomOffset = null;
var cachedSidebar = null;
var headings = getHeadings();
function isDesktop() { function isDesktop() {
return desktopQuery.matches; return desktopQuery.matches;
@@ -26,14 +62,13 @@ document.addEventListener('DOMContentLoaded', function () {
} }
function getHeadings() { function getHeadings() {
return links.map(function (link) { return links
return { .map(function (link) {
link: link, return { link: link, target: getTarget(link) };
target: getTarget(link) })
}; .filter(function (item) {
}).filter(function (item) { return !!item.target;
return !!item.target; });
});
} }
function getArticleElement() { function getArticleElement() {
@@ -56,6 +91,27 @@ document.addEventListener('DOMContentLoaded', function () {
return tocBottomOffset; return tocBottomOffset;
} }
function getRealSidebar() {
if (cachedSidebar) return cachedSidebar;
var candidates = Array.prototype.slice.call(
document.querySelectorAll('aside, .sidebar, #secondary')
);
cachedSidebar = candidates
.filter(function (el) {
var rect = el.getBoundingClientRect();
return rect.width > 200 && rect.height > 200;
})
.sort(function (a, b) {
var rectA = a.getBoundingClientRect();
var rectB = b.getBoundingClientRect();
return rectB.left - rectA.left;
})[0] || null;
return cachedSidebar;
}
function syncPosition() { function syncPosition() {
if (!isDesktop()) { if (!isDesktop()) {
document.documentElement.style.setProperty('--zeitfresser-toc-top', stickyTop + 'px'); document.documentElement.style.setProperty('--zeitfresser-toc-top', stickyTop + 'px');
@@ -64,36 +120,41 @@ document.addEventListener('DOMContentLoaded', function () {
return; return;
} }
var titleRect = title.getBoundingClientRect();
var scrollTop = window.scrollY || window.pageYOffset || 0; var scrollTop = window.scrollY || window.pageYOffset || 0;
var titleRect = title.getBoundingClientRect();
var contentColumn = document.querySelector( // 🔥 bessere Content-Erkennung
'.inside-page .main-wrapper > *:first-child, ' + var contentColumn =
'.inside-page .main-wrapper .primary-content, ' + document.querySelector('.inside-page .main-wrapper > section') ||
'.inside-page .main-wrapper #primary, ' + document.querySelector('#primary') ||
'.inside-page .main-wrapper main' document.querySelector('.content-area') ||
); title;
var sidebar = document.querySelector( if (!contentColumn) return;
'.inside-page .main-wrapper > aside, ' +
'.inside-page .main-wrapper .widget-area, ' +
'.inside-page .main-wrapper #secondary, ' +
'.inside-page .main-wrapper .sidebar'
);
var contentRect = contentColumn ? contentColumn.getBoundingClientRect() : titleRect; var sidebar = getRealSidebar();
var contentRect = contentColumn.getBoundingClientRect();
var sidebarRect = sidebar ? sidebar.getBoundingClientRect() : null; var sidebarRect = sidebar ? sidebar.getBoundingClientRect() : null;
var mirroredGap = 56; var gap = 48;
if (sidebarRect) { if (sidebarRect) {
mirroredGap = Math.max(Math.round(sidebarRect.left - contentRect.right), 40); gap = Math.abs(sidebarRect.left - contentRect.right);
gap = Math.max(32, Math.min(gap, 120));
} }
var maxWidth = Math.max(Math.round(contentRect.left - mirroredGap - 24), 180); var maxWidth = Math.max(Math.round(contentRect.left - gap - 24), 180);
var tocWidth = Math.max(190, Math.min(250, maxWidth)); var tocWidth = Math.max(220, Math.min(260, maxWidth));
var tocLeft = Math.max(24, Math.round(contentRect.left - mirroredGap - tocWidth));
var tocTop = Math.max(stickyTop, Math.round(titleRect.top + scrollTop + 14)); var tocLeft = Math.max(
24,
Math.round(contentRect.left - gap - tocWidth)
);
var tocTop = Math.max(
stickyTop,
Math.round(titleRect.top + scrollTop + 14)
);
document.documentElement.style.setProperty('--zeitfresser-toc-top', tocTop + 'px'); document.documentElement.style.setProperty('--zeitfresser-toc-top', tocTop + 'px');
document.documentElement.style.setProperty('--zeitfresser-toc-left', tocLeft + 'px'); document.documentElement.style.setProperty('--zeitfresser-toc-left', tocLeft + 'px');
@@ -107,7 +168,6 @@ document.addEventListener('DOMContentLoaded', function () {
} }
var article = getArticleElement(); var article = getArticleElement();
if (!article) { if (!article) {
toc.style.transform = ''; toc.style.transform = '';
return; return;
@@ -116,7 +176,6 @@ document.addEventListener('DOMContentLoaded', function () {
toc.style.transform = ''; toc.style.transform = '';
var scrollTop = window.scrollY || window.pageYOffset; var scrollTop = window.scrollY || window.pageYOffset;
var articleRect = article.getBoundingClientRect(); var articleRect = article.getBoundingClientRect();
var articleBottom = articleRect.top + scrollTop + articleRect.height; var articleBottom = articleRect.top + scrollTop + articleRect.height;
@@ -126,7 +185,6 @@ document.addEventListener('DOMContentLoaded', function () {
var tocBottom = tocTop + tocHeight; var tocBottom = tocTop + tocHeight;
var offset = getTocBottomOffset(); var offset = getTocBottomOffset();
var maxBottom = articleBottom - offset; var maxBottom = articleBottom - offset;
var overflow = Math.ceil(tocBottom - maxBottom); var overflow = Math.ceil(tocBottom - maxBottom);
@@ -152,31 +210,40 @@ document.addEventListener('DOMContentLoaded', function () {
if (!progressBar) return; if (!progressBar) return;
var article = getArticleElement(); var article = getArticleElement();
if (!article) { if (!article) {
progressBar.style.width = '0%'; progressBar.style.width = '0%';
return; return;
} }
var rect = article.getBoundingClientRect(); var rect = article.getBoundingClientRect();
var total = Math.max(article.offsetHeight - window.innerHeight, 1); var total = Math.max(
var progress = Math.min(Math.max((-rect.top / total) * 100, 0), 100); Math.max(article.scrollHeight, article.offsetHeight) - window.innerHeight,
1
);
var progress = Math.min(
Math.max((-rect.top / total) * 100, 0),
100
);
progressBar.style.width = progress + '%'; progressBar.style.width = progress + '%';
} }
function updateActiveHeading() { function updateActiveHeading() {
var headings = getHeadings();
if (!headings.length) return; if (!headings.length) return;
var currentId = headings[0].target.id; var currentId = headings[0].target.id;
var triggerY = headingOffset + 24; var triggerY = headingOffset + 24;
headings.forEach(function (item) { for (var i = 0; i < headings.length; i++) {
if (item.target.getBoundingClientRect().top <= triggerY) { var rectTop = headings[i].target.getBoundingClientRect().top;
currentId = item.target.id;
if (rectTop <= triggerY) {
currentId = headings[i].target.id;
} else {
break;
} }
}); }
setActiveLink(currentId); setActiveLink(currentId);
} }
@@ -202,7 +269,10 @@ document.addEventListener('DOMContentLoaded', function () {
event.preventDefault(); event.preventDefault();
var top = target.getBoundingClientRect().top + window.scrollY - headingOffset; var top =
target.getBoundingClientRect().top +
window.scrollY -
headingOffset;
window.scrollTo({ window.scrollTo({
top: top, top: top,
@@ -214,18 +284,26 @@ document.addEventListener('DOMContentLoaded', function () {
}); });
if (nav) { if (nav) {
nav.addEventListener('wheel', function (event) { nav.addEventListener(
var canScroll = nav.scrollHeight > nav.clientHeight; 'wheel',
if (!canScroll) return; function (event) {
var canScroll = nav.scrollHeight > nav.clientHeight;
if (!canScroll) return;
var atTop = nav.scrollTop <= 0; var atTop = nav.scrollTop <= 0;
var atBottom = Math.ceil(nav.scrollTop + nav.clientHeight) >= nav.scrollHeight; var atBottom =
Math.ceil(nav.scrollTop + nav.clientHeight) >= nav.scrollHeight;
if ((event.deltaY < 0 && !atTop) || (event.deltaY > 0 && !atBottom)) { if (
event.preventDefault(); (event.deltaY < 0 && !atTop) ||
nav.scrollTop += event.deltaY; (event.deltaY > 0 && !atBottom)
} ) {
}, { passive: false }); event.preventDefault();
nav.scrollTop += event.deltaY;
}
},
{ passive: false }
);
} }
// Initial run // Initial run
@@ -240,4 +318,8 @@ document.addEventListener('DOMContentLoaded', function () {
window.addEventListener('scroll', onViewportChange, { passive: true }); window.addEventListener('scroll', onViewportChange, { passive: true });
window.addEventListener('resize', onViewportChange, { passive: true }); window.addEventListener('resize', onViewportChange, { passive: true });
window.addEventListener('load', function () {
syncPosition();
});
}); });
-7
View File
@@ -1,7 +0,0 @@
/* Align lists in the Classic Editor */
ul, ol {
margin-left: 0 !important;
padding-left: 1.0em !important;
list-style-position: inside !important;
}
+23 -22
View File
@@ -24,38 +24,34 @@ if ( ! defined( 'ZEITFRESSER_IMAGE_OPTIMIZATION_VERSION' ) ) {
/** /**
* ------------------------------------------------------------------------ * ------------------------------------------------------------------------
* Core Modules * Customizer
* ------------------------------------------------------------------------ * ------------------------------------------------------------------------
*/ */
require get_template_directory() . '/inc/customizer/core-settings.php';
// Helpers (foundation) require get_template_directory() . '/inc/customizer/general-settings.php';
require get_template_directory() . '/inc/helpers/zeitfresser-helpers.php'; require get_template_directory() . '/inc/customizer/layout-settings.php';
require get_template_directory() . '/inc/customizer/toc-settings.php';
// Theme logic require get_template_directory() . '/inc/customizer/social-settings.php';
require get_template_directory() . '/inc/zeitfresser-toc.php'; require get_template_directory() . '/inc/customizer/image-optimizer-settings.php';
// Performance layer
require get_template_directory() . '/inc/performance/performance-tools.php';
/** /**
* ------------------------------------------------------------------------ * ------------------------------------------------------------------------
* Customizer (modular) * Utilities
* ------------------------------------------------------------------------ * ------------------------------------------------------------------------
*/ */
require get_template_directory() . '/inc/customizer/core.php'; require get_template_directory() . '/inc/utilities/helpers.php';
require get_template_directory() . '/inc/customizer/general.php'; require get_template_directory() . '/inc/utilities/template-tags.php';
require get_template_directory() . '/inc/customizer/layout.php'; require get_template_directory() . '/inc/utilities/template-functions.php';
require get_template_directory() . '/inc/customizer/toc.php'; require get_template_directory() . '/inc/utilities/pagination.php';
require get_template_directory() . '/inc/customizer/social.php'; require get_template_directory() . '/inc/utilities/toc.php';
/** /**
* ------------------------------------------------------------------------ * ------------------------------------------------------------------------
* Theme Utilities * Tools
* ------------------------------------------------------------------------ * ------------------------------------------------------------------------
*/ */
require get_template_directory() . '/inc/template-tags.php'; require get_template_directory() . '/inc/tools/image-optimizer.php';
require get_template_directory() . '/inc/template-functions.php'; require get_template_directory() . '/inc/tools/code-block.php';
require get_template_directory() . '/inc/pagination.php';
/** /**
* ------------------------------------------------------------------------ * ------------------------------------------------------------------------
@@ -147,8 +143,6 @@ function zeitfresser_setup() {
register_nav_menus( array( register_nav_menus( array(
'menu-1' => esc_html__( 'Primary', 'zeitfresser' ), 'menu-1' => esc_html__( 'Primary', 'zeitfresser' ),
)); ));
add_editor_style( 'editor-style.css' );
} }
add_action( 'after_setup_theme', 'zeitfresser_setup' ); add_action( 'after_setup_theme', 'zeitfresser_setup' );
@@ -253,6 +247,13 @@ function zeitfresser_scripts() {
} }
add_action( 'wp_enqueue_scripts', 'zeitfresser_scripts', 10 ); add_action( 'wp_enqueue_scripts', 'zeitfresser_scripts', 10 );
// Editor Styles
function zeitfresser_editor_styles_setup() {
add_theme_support( 'editor-styles' );
add_editor_style( 'assets/css/editor.css' );
}
add_action( 'after_setup_theme', 'zeitfresser_editor_styles_setup' );
/** /**
* ------------------------------------------------------------------------ * ------------------------------------------------------------------------
* Performance Tweaks * Performance Tweaks
+58
View File
@@ -0,0 +1,58 @@
<?php
/**
* Theme Customizer Core
*
* @package zeitfresser
*/
function zeitfresser_customize_register( $wp_customize ) {
// Live Preview support
$wp_customize->get_setting( 'blogname' )->transport = 'postMessage';
$wp_customize->get_setting( 'blogdescription' )->transport = 'postMessage';
$wp_customize->get_setting( 'header_textcolor' )->transport = 'postMessage';
if ( isset( $wp_customize->selective_refresh ) ) {
$wp_customize->selective_refresh->add_partial(
'blogname',
array(
'selector' => '.site-title a',
'render_callback' => 'zeitfresser_customize_partial_blogname',
)
);
$wp_customize->selective_refresh->add_partial(
'blogdescription',
array(
'selector' => '.site-description',
'render_callback' => 'zeitfresser_customize_partial_blogdescription',
)
);
}
}
add_action( 'customize_register', 'zeitfresser_customize_register' );
/**
* Partial refresh helpers
*/
function zeitfresser_customize_partial_blogname() {
bloginfo( 'name' );
}
function zeitfresser_customize_partial_blogdescription() {
bloginfo( 'description' );
}
/**
* Live preview JS
*/
function zeitfresser_customize_preview_js() {
wp_enqueue_script(
'zeitfresser-customizer',
get_template_directory_uri() . '/js/customizer.js',
array( 'customize-preview' ),
ZEITFRESSER_VERSION,
true
);
}
add_action( 'customize_preview_init', 'zeitfresser_customize_preview_js' );
+152
View File
@@ -0,0 +1,152 @@
<?php
/**
* General Theme Options
*
* @package zeitfresser
*/
add_action( 'customize_register', 'zeitfresser_general_options' );
function zeitfresser_general_options( $wp_customize ) {
/**
* 🔥 Custom Heading Control
*/
if ( class_exists( 'WP_Customize_Control' ) ) {
class ZTFR_Customize_Heading_Control extends WP_Customize_Control {
public $type = 'ztfr-heading';
public function render_content() {
?>
<span style="display:block; font-weight:600; font-size:14px; margin:15px 0 5px;">
<?php echo esc_html( $this->label ); ?>
</span>
<?php
}
}
}
/**
* General Section
*/
if ( ! $wp_customize->get_section( 'ztfr_general' ) ) {
$wp_customize->add_section(
'ztfr_general',
array(
'title' => 'General Options',
'priority' => 30,
)
);
}
/**
* ------------------------
* HEADER
* ------------------------
*/
$wp_customize->add_setting( 'ztfr_header_heading', array(
'sanitize_callback' => 'sanitize_text_field',
));
$wp_customize->add_control(
new ZTFR_Customize_Heading_Control(
$wp_customize,
'ztfr_header_heading',
array(
'label' => 'Header',
'section' => 'ztfr_general',
'priority' => 1,
)
)
);
/**
* Show Site Title
*/
$wp_customize->add_setting(
'show_hide_site_title',
array(
'default' => true,
'sanitize_callback' => 'wp_validate_boolean',
)
);
$wp_customize->add_control(
'show_hide_site_title',
array(
'type' => 'checkbox',
'section' => 'ztfr_general',
'label' => 'Show Site Title',
'priority' => 2,
)
);
/**
* Show Tagline
*/
$wp_customize->add_setting(
'show_hide_site_tagline',
array(
'default' => true,
'sanitize_callback' => 'wp_validate_boolean',
)
);
$wp_customize->add_control(
'show_hide_site_tagline',
array(
'type' => 'checkbox',
'section' => 'ztfr_general',
'label' => 'Show Tagline',
'priority' => 3,
)
);
/**
* ------------------------
* GRID
* ------------------------
*/
$wp_customize->add_setting( 'ztfr_grid_heading', array(
'sanitize_callback' => 'sanitize_text_field',
));
$wp_customize->add_control(
new ZTFR_Customize_Heading_Control(
$wp_customize,
'ztfr_grid_heading',
array(
'label' => 'Grid',
'section' => 'ztfr_general',
'priority' => 20,
)
)
);
/**
* Excerpt Length
*/
$wp_customize->add_setting(
'post_snippet_excerpt_size',
array(
'default' => 25,
'sanitize_callback' => 'absint',
)
);
$wp_customize->add_control(
'post_snippet_excerpt_size',
array(
'type' => 'number',
'section' => 'ztfr_general',
'label' => 'Excerpt Length (Post Cards)',
'description' => 'Number of words shown in post previews.',
'priority' => 21,
'input_attrs' => array(
'min' => 5,
'max' => 100,
'step' => 1,
),
)
);
}
-90
View File
@@ -1,90 +0,0 @@
<?php
/**
* General Theme Options
*
* @package zeitfresser
*/
add_action( 'customize_register', 'zeitfresser_general_options' );
function zeitfresser_general_options( $wp_customize ) {
/**
* General Section (falls nicht schon vorhanden)
*/
if ( ! $wp_customize->get_section( 'ztfr_general' ) ) {
$wp_customize->add_section(
'ztfr_general',
array(
'title' => 'General Options',
'priority' => 30,
)
);
}
/**
* Excerpt Length
*/
$wp_customize->add_setting(
'post_snippet_excerpt_size',
array(
'default' => 20,
'sanitize_callback' => 'absint',
)
);
$wp_customize->add_control(
'post_snippet_excerpt_size',
array(
'type' => 'number',
'section' => 'ztfr_general',
'label' => 'Excerpt Length (Post Cards)',
'description' => 'Number of words shown in post previews.',
'input_attrs' => array(
'min' => 5,
'max' => 100,
'step' => 1,
),
)
);
/**
* Show Site Title
*/
$wp_customize->add_setting(
'show_hide_site_title',
array(
'default' => true,
'sanitize_callback' => 'wp_validate_boolean',
)
);
$wp_customize->add_control(
'show_hide_site_title',
array(
'type' => 'checkbox',
'section' => 'ztfr_general',
'label' => 'Show Site Title',
)
);
/**
* Show Tagline
*/
$wp_customize->add_setting(
'show_hide_site_tagline',
array(
'default' => true,
'sanitize_callback' => 'wp_validate_boolean',
)
);
$wp_customize->add_control(
'show_hide_site_tagline',
array(
'type' => 'checkbox',
'section' => 'ztfr_general',
'label' => 'Show Tagline',
)
);
}
@@ -1,42 +1,24 @@
<?php <?php
/** /**
* Theme Customizer Core * Image Optimizer (Settings + UI)
* *
* @package zeitfresser * @package zeitfresser
*/ */
function zeitfresser_customize_register( $wp_customize ) { /**
* ------------------------------------------------------------------------
// Live Preview support * Settings
$wp_customize->get_setting( 'blogname' )->transport = 'postMessage'; * ------------------------------------------------------------------------
$wp_customize->get_setting( 'blogdescription' )->transport = 'postMessage'; */
$wp_customize->get_setting( 'header_textcolor' )->transport = 'postMessage'; function zeitfresser_customize_image_optimizer_settings( $wp_customize ) {
if ( isset( $wp_customize->selective_refresh ) ) {
$wp_customize->selective_refresh->add_partial(
'blogname',
array(
'selector' => '.site-title a',
'render_callback' => 'zeitfresser_customize_partial_blogname',
)
);
$wp_customize->selective_refresh->add_partial(
'blogdescription',
array(
'selector' => '.site-description',
'render_callback' => 'zeitfresser_customize_partial_blogdescription',
)
);
}
/** /**
* Performance Tools Section * Image Optimizer Section
*/ */
$wp_customize->add_section( $wp_customize->add_section(
'ztfr_performance_tools', 'ztfr_image_optimizer',
array( array(
'title' => 'Performance Tools Settings', 'title' => 'Image Optimizer',
'priority' => 160, 'priority' => 160,
) )
); );
@@ -56,7 +38,7 @@ function zeitfresser_customize_register( $wp_customize ) {
'ztfr_auto_optimize', 'ztfr_auto_optimize',
array( array(
'type' => 'checkbox', 'type' => 'checkbox',
'section' => 'ztfr_performance_tools', 'section' => 'ztfr_image_optimizer',
'label' => 'Auto Optimize Pictures on Upload', 'label' => 'Auto Optimize Pictures on Upload',
'description' => 'Automatically converts images to AVIF/WebP.', 'description' => 'Automatically converts images to AVIF/WebP.',
) )
@@ -77,43 +59,21 @@ function zeitfresser_customize_register( $wp_customize ) {
'ztfr_auto_delete', 'ztfr_auto_delete',
array( array(
'type' => 'checkbox', 'type' => 'checkbox',
'section' => 'ztfr_performance_tools', 'section' => 'ztfr_image_optimizer',
'label' => 'Auto Delete Original Pictures', 'label' => 'Auto Delete Original Pictures',
'description' => 'Deletes originals after optimization.', 'description' => 'Deletes originals after optimization.',
) )
); );
} }
add_action( 'customize_register', 'zeitfresser_customize_register' ); add_action( 'customize_register', 'zeitfresser_customize_image_optimizer_settings' );
/** /**
* Partial refresh helpers * ------------------------------------------------------------------------
* UI Logic (JS)
* ------------------------------------------------------------------------
*/ */
function zeitfresser_customize_partial_blogname() { function zeitfresser_customize_image_optimizer_ui() {
bloginfo( 'name' );
}
function zeitfresser_customize_partial_blogdescription() {
bloginfo( 'description' );
}
/**
* Live preview JS
*/
function zeitfresser_customize_preview_js() {
wp_enqueue_script(
'zeitfresser-customizer',
get_template_directory_uri() . '/js/customizer.js',
array( 'customize-preview' ),
ZEITFRESSER_VERSION,
true
);
}
add_action( 'customize_preview_init', 'zeitfresser_customize_preview_js' );
/**
* Dependency UI logic
*/
function zeitfresser_customize_controls_dependency_js() {
?> ?>
<script> <script>
(function() { (function() {
@@ -193,7 +153,6 @@ function zeitfresser_customize_controls_dependency_js() {
return; return;
} }
// Retry max 10x
if (attempts < 10) { if (attempts < 10) {
attempts++; attempts++;
setTimeout(tryInit, 200); setTimeout(tryInit, 200);
@@ -225,20 +184,27 @@ function zeitfresser_customize_controls_dependency_js() {
</script> </script>
<?php <?php
} }
add_action( 'customize_controls_enqueue_scripts', 'zeitfresser_customize_controls_dependency_js' ); add_action( 'customize_controls_enqueue_scripts', 'zeitfresser_customize_image_optimizer_ui' );
/** /**
* Small UI polish * ------------------------------------------------------------------------
* UI Styles
* ------------------------------------------------------------------------
*/ */
add_action( 'customize_controls_enqueue_scripts', function() { function zeitfresser_customize_image_optimizer_ui_styles() {
?> ?>
<style> <style>
#customize-control-ztfr_auto_optimize > label, #customize-control-ztfr_auto_optimize > label,
#customize-control-ztfr_auto_delete > label { #customize-control-ztfr_auto_delete > label {
display:flex; display:flex;
align-items:flex-start; align-items:flex-start;
gap:6px; gap:6px;
}
</style>
<?php
} }
</style> add_action(
<?php 'customize_controls_enqueue_scripts',
}); 'zeitfresser_customize_image_optimizer_ui_styles'
);
@@ -27,7 +27,7 @@ function zeitfresser_layout_options( $wp_customize ) {
'section' => 'ztfr_general', 'section' => 'ztfr_general',
'label' => esc_html__( 'Container Width', 'zeitfresser' ), 'label' => esc_html__( 'Container Width', 'zeitfresser' ),
'description' => esc_html__( 'Maximum width of the content container in pixels.', 'zeitfresser' ), 'description' => esc_html__( 'Maximum width of the content container in pixels.', 'zeitfresser' ),
'priority' => 10, 'priority' => 22,
'input_attrs' => array( 'input_attrs' => array(
'min' => 800, 'min' => 800,
'max' => 2000, 'max' => 2000,
@@ -47,7 +47,7 @@ function zeitfresser_container_width_dynamic_css() {
$container_width = (int) get_theme_mod( 'container_width' ); $container_width = (int) get_theme_mod( 'container_width' );
if ( $container_width <= 0 ) { if ( $container_width <= 0 ) {
$container_width = 1140; $container_width = 1400;
} }
echo '<style>:root{--container-width:' . esc_attr( $container_width ) . 'px;}</style>'; echo '<style>:root{--container-width:' . esc_attr( $container_width ) . 'px;}</style>';
@@ -6,11 +6,6 @@
*/ */
if ( ! function_exists( 'zeitfresser_get_social_links' ) ) { if ( ! function_exists( 'zeitfresser_get_social_links' ) ) {
/**
* Return supported social networks.
*
* @return array<string,string>
*/
function zeitfresser_get_social_links() { function zeitfresser_get_social_links() {
return array( return array(
'facebook' => esc_html__( 'Facebook', 'zeitfresser' ), 'facebook' => esc_html__( 'Facebook', 'zeitfresser' ),
@@ -31,31 +26,52 @@ add_action( 'customize_register', 'zeitfresser_social_links' );
function zeitfresser_social_links( $wp_customize ) { function zeitfresser_social_links( $wp_customize ) {
$social_links = zeitfresser_get_social_links(); /**
* 🔥 Heading Control (falls noch nicht vorhanden)
*/
if ( class_exists( 'WP_Customize_Control' ) && ! class_exists( 'ZTFR_Customize_Heading_Control' ) ) {
class ZTFR_Customize_Heading_Control extends WP_Customize_Control {
public $type = 'ztfr-heading';
public function render_content() {
?>
<span style="display:block; font-weight:600; font-size:14px; margin:15px 0 5px;">
<?php echo esc_html( $this->label ); ?>
</span>
<?php
}
}
}
/** /**
* Section Divider * ------------------------
* SOCIAL HEADING
* ------------------------
*/ */
$wp_customize->add_setting( $wp_customize->add_setting(
'ztfr_social_heading', 'ztfr_social_heading',
array( array(
'sanitize_callback' => 'wp_kses_post', 'sanitize_callback' => 'sanitize_text_field',
) )
); );
$wp_customize->add_control( $wp_customize->add_control(
'ztfr_social_heading', new ZTFR_Customize_Heading_Control(
array( $wp_customize,
'section' => 'ztfr_general', 'ztfr_social_heading',
'type' => 'hidden', array(
'description' => '<hr><strong>' . esc_html__( 'Social Links', 'zeitfresser' ) . '</strong>', 'label' => esc_html__( 'Social Links', 'zeitfresser' ),
'priority' => 30, 'section' => 'ztfr_general',
'priority' => 30,
)
) )
); );
/** /**
* Social URLs * Social URLs
*/ */
$social_links = zeitfresser_get_social_links();
$priority = 31; $priority = 31;
foreach ( $social_links as $key => $label ) { foreach ( $social_links as $key => $label ) {
@@ -10,27 +10,48 @@ add_action( 'customize_register', 'zeitfresser_toc_options' );
function zeitfresser_toc_options( $wp_customize ) { function zeitfresser_toc_options( $wp_customize ) {
/** /**
* Section Divider (UI only) * 🔥 Heading Control (falls noch nicht vorhanden)
*/
if ( class_exists( 'WP_Customize_Control' ) && ! class_exists( 'ZTFR_Customize_Heading_Control' ) ) {
class ZTFR_Customize_Heading_Control extends WP_Customize_Control {
public $type = 'ztfr-heading';
public function render_content() {
?>
<span style="display:block; font-weight:600; font-size:14px; margin:15px 0 5px;">
<?php echo esc_html( $this->label ); ?>
</span>
<?php
}
}
}
/**
* ------------------------
* TOC HEADING
* ------------------------
*/ */
$wp_customize->add_setting( $wp_customize->add_setting(
'ztfr_toc_heading', 'ztfr_toc_heading',
array( array(
'sanitize_callback' => 'wp_kses_post', 'sanitize_callback' => 'sanitize_text_field',
) )
); );
$wp_customize->add_control( $wp_customize->add_control(
'ztfr_toc_heading', new ZTFR_Customize_Heading_Control(
array( $wp_customize,
'section' => 'ztfr_general', 'ztfr_toc_heading',
'type' => 'hidden', array(
'description' => '<hr><strong>' . esc_html__( 'Article TOC', 'zeitfresser' ) . '</strong>', 'label' => esc_html__( 'TOC', 'zeitfresser' ),
'priority' => 20, 'section' => 'ztfr_general',
'priority' => 10,
)
) )
); );
/** /**
* Toggle TOC * Show TOC
*/ */
$wp_customize->add_setting( $wp_customize->add_setting(
'show_article_toc', 'show_article_toc',
@@ -47,7 +68,7 @@ function zeitfresser_toc_options( $wp_customize ) {
'section' => 'ztfr_general', 'section' => 'ztfr_general',
'label' => esc_html__( 'Show Article TOC', 'zeitfresser' ), 'label' => esc_html__( 'Show Article TOC', 'zeitfresser' ),
'description' => esc_html__( 'Enable floating TOC on single posts.', 'zeitfresser' ), 'description' => esc_html__( 'Enable floating TOC on single posts.', 'zeitfresser' ),
'priority' => 21, 'priority' => 11,
) )
); );
@@ -67,9 +88,9 @@ function zeitfresser_toc_options( $wp_customize ) {
array( array(
'type' => 'number', 'type' => 'number',
'section' => 'ztfr_general', 'section' => 'ztfr_general',
'label' => esc_html__( 'Minimum Headlines for TOC', 'zeitfresser' ), 'label' => esc_html__( 'Minimum Headlines', 'zeitfresser' ),
'description' => esc_html__( 'TOC appears only if this number of headings is reached.', 'zeitfresser' ), 'description' => esc_html__( 'TOC appears only if this number of headings is reached.', 'zeitfresser' ),
'priority' => 22, 'priority' => 12,
'input_attrs' => array( 'input_attrs' => array(
'min' => 1, 'min' => 1,
'max' => 50, 'max' => 50,
+211
View File
@@ -0,0 +1,211 @@
<?php
/**
* Simplified Code Block Feature
*
* Handles:
* - Frontend assets
* - Editor assets
* - Classic Editor button
* - Gutenberg block
* - Server-side wrapping for code blocks
*
* @package Zeitfresser
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Get asset version from file modification time.
*
* @param string $relative_path Relative path inside the theme directory.
* @return string
*/
function ztfr_code_asset_version( $relative_path ) {
$file_path = get_template_directory() . $relative_path;
if ( file_exists( $file_path ) ) {
return (string) filemtime( $file_path );
}
return (string) ZEITFRESSER_VERSION;
}
/**
* Enqueue frontend assets.
*/
function ztfr_enqueue_code_assets() {
if ( is_admin() ) {
return;
}
$code_css_version = ztfr_code_asset_version( '/assets/css/code.css' );
$prism_version = ztfr_code_asset_version( '/assets/js/prism.js' );
$code_js_version = ztfr_code_asset_version( '/assets/js/code-block.js' );
wp_enqueue_style(
'ztfr-code',
get_template_directory_uri() . '/assets/css/code.css',
[],
$code_css_version
);
wp_enqueue_script(
'ztfr-prism',
get_template_directory_uri() . '/assets/js/prism.js',
[],
$prism_version,
true
);
/**
* Disable Prism auto-highlighting.
*
* Prism auto-runs on DOM ready unless Prism.manual is set before the core
* script executes. We disable auto-run so highlighting happens only once
* in our custom script after the DOM is ready.
*/
wp_add_inline_script(
'ztfr-prism',
'window.Prism = window.Prism || {}; window.Prism.manual = true;',
'before'
);
wp_enqueue_script(
'ztfr-code-block',
get_template_directory_uri() . '/assets/js/code-block.js',
[ 'ztfr-prism' ],
$code_js_version,
true
);
}
add_action( 'wp_enqueue_scripts', 'ztfr_enqueue_code_assets' );
/**
* Enqueue block editor assets.
*/
function ztfr_enqueue_code_block_editor_assets() {
$editor_js_version = ztfr_code_asset_version( '/assets/js/editor.js' );
$code_css_version = ztfr_code_asset_version( '/assets/css/code.css' );
$editor_css_version = ztfr_code_asset_version( '/assets/css/editor.css' );
wp_enqueue_script(
'ztfr-code-editor',
get_template_directory_uri() . '/assets/js/editor.js',
[
'wp-blocks',
'wp-element',
'wp-i18n',
'wp-components',
'wp-block-editor',
],
$editor_js_version,
true
);
wp_enqueue_style(
'ztfr-code-editor-preview',
get_template_directory_uri() . '/assets/css/code.css',
[],
$code_css_version
);
wp_enqueue_style(
'ztfr-editor',
get_template_directory_uri() . '/assets/css/editor.css',
[],
$editor_css_version
);
}
add_action( 'enqueue_block_editor_assets', 'ztfr_enqueue_code_block_editor_assets' );
/**
* Register Classic Editor button only for users who can edit and use rich text.
*/
function ztfr_register_classic_code_button() {
if ( ! current_user_can( 'edit_posts' ) && ! current_user_can( 'edit_pages' ) ) {
return;
}
if ( 'true' !== get_user_option( 'rich_editing' ) ) {
return;
}
add_filter( 'mce_external_plugins', 'ztfr_add_classic_code_plugin' );
add_filter( 'mce_buttons', 'ztfr_add_classic_code_button' );
}
add_action( 'admin_init', 'ztfr_register_classic_code_button' );
/**
* Add TinyMCE plugin script.
*
* @param array $plugins TinyMCE plugins.
* @return array
*/
function ztfr_add_classic_code_plugin( $plugins ) {
$plugins['ztfr_code_block'] = get_template_directory_uri() . '/assets/js/editor.js';
return $plugins;
}
/**
* Add TinyMCE toolbar button.
*
* @param array $buttons TinyMCE buttons.
* @return array
*/
function ztfr_add_classic_code_button( $buttons ) {
$buttons[] = 'ztfr_code_block';
return $buttons;
}
/**
* Wrap code blocks server-side.
*
* This keeps Classic Editor, Gutenberg and pasted raw code blocks
* compatible with the frontend styling without runtime DOM manipulation.
*
* @param string $content Post content.
* @return string
*/
function ztfr_wrap_code_blocks( $content ) {
if ( is_admin() ) {
return $content;
}
if ( false === strpos( $content, '<pre' ) ) {
return $content;
}
$content = preg_replace_callback(
'/<pre([^>]*)><code([^>]*)>(.*?)<\/code><\/pre>/s',
function ( $matches ) {
$pre_attrs = $matches[1];
$code_attrs = $matches[2];
$code = $matches[3];
if ( false === strpos( $pre_attrs, 'language-' ) ) {
$pre_attrs .= ' class="language-yaml"';
}
if ( false === strpos( $code_attrs, 'language-' ) ) {
$code_attrs .= ' class="language-yaml"';
}
return sprintf(
'<div class="ztfr-code"><pre%s><code%s>%s</code></pre></div>',
$pre_attrs,
$code_attrs,
$code
);
},
$content
);
return $content;
}
add_filter( 'the_content', 'ztfr_wrap_code_blocks', 20 );
@@ -1,6 +1,6 @@
<?php <?php
/** /**
* Performance tools for existing media. * Image Optimizer
* *
* @package zeitfresser * @package zeitfresser
*/ */
@@ -12,16 +12,16 @@ if ( ! defined( 'ABSPATH' ) ) {
/** /**
* Register admin page * Register admin page
*/ */
function zeitfresser_register_performance_tools_page() { function zeitfresser_register_image_optimizer_page() {
add_theme_page( add_theme_page(
'Performance Tools', 'Image Optimizer',
'Performance Tools', 'Image Optimizer',
'manage_options', 'manage_options',
'zeitfresser-performance-tools', 'zeitfresser-image-optimizer',
'zeitfresser_render_performance_tools_page' 'zeitfresser_render_image_optimizer_page'
); );
} }
add_action( 'admin_menu', 'zeitfresser_register_performance_tools_page' ); add_action( 'admin_menu', 'zeitfresser_register_image_optimizer_page' );
/** /**
* Count pending images * Count pending images
@@ -427,7 +427,7 @@ function zeitfresser_ajax_optimize_images() {
wp_send_json_error(); wp_send_json_error();
} }
check_ajax_referer( 'zeitfresser_performance_tools', 'nonce' ); check_ajax_referer( 'zeitfresser_image_optimizer', 'nonce' );
$results = zeitfresser_process_legacy_images_batch( 25 ); $results = zeitfresser_process_legacy_images_batch( 25 );
@@ -465,7 +465,7 @@ function zeitfresser_ajax_delete_originals() {
wp_send_json_error(); wp_send_json_error();
} }
check_ajax_referer( 'zeitfresser_performance_tools', 'nonce' ); check_ajax_referer( 'zeitfresser_image_optimizer', 'nonce' );
$deleted = zeitfresser_delete_originals_batch( 10 ); $deleted = zeitfresser_delete_originals_batch( 10 );
$total = zeitfresser_get_total_originals_count(); $total = zeitfresser_get_total_originals_count();
@@ -486,7 +486,7 @@ add_action( 'wp_ajax_zeitfresser_delete_originals', 'zeitfresser_ajax_delete_ori
/** /**
* Render UI * Render UI
*/ */
function zeitfresser_render_performance_tools_page() { function zeitfresser_render_image_optimizer_page() {
if ( ! current_user_can( 'manage_options' ) ) { if ( ! current_user_can( 'manage_options' ) ) {
return; return;
@@ -519,7 +519,7 @@ function zeitfresser_render_performance_tools_page() {
?> ?>
<div class="wrap"> <div class="wrap">
<h1>Zeitfresser Performance Tools</h1> <h1>Zeitfresser Image Optimizer</h1>
<div class="notice notice-info" style="max-width:800px;margin-top:20px;"> <div class="notice notice-info" style="max-width:800px;margin-top:20px;">
<p> <p>
@@ -532,7 +532,7 @@ function zeitfresser_render_performance_tools_page() {
Once optimized, original images can be deleted to save disk space.<br><br> Once optimized, original images can be deleted to save disk space.<br><br>
<strong>Automation:</strong><br> <strong>Automation:</strong><br>
You can enable automatic optimization on upload in the Customizer under <em>Performance Tools Settings</em>.<br> You can enable automatic optimization on upload in the Customizer under <em>Image Optimizer Settings</em>.<br>
Optionally, original images can also be deleted automatically after successful optimization.<br><br> Optionally, original images can also be deleted automatically after successful optimization.<br><br>
<strong>Safety:</strong><br> <strong>Safety:</strong><br>
@@ -653,7 +653,7 @@ function deleteBatch() {
headers: {'Content-Type': 'application/x-www-form-urlencoded'}, headers: {'Content-Type': 'application/x-www-form-urlencoded'},
body: new URLSearchParams({ body: new URLSearchParams({
action: 'zeitfresser_delete_originals', action: 'zeitfresser_delete_originals',
nonce: '<?php echo wp_create_nonce('zeitfresser_performance_tools'); ?>' nonce: '<?php echo wp_create_nonce('zeitfresser_image_optimizer'); ?>'
}) })
}) })
.then(res => res.json()) .then(res => res.json())
@@ -732,7 +732,7 @@ function processBatch() {
headers: {'Content-Type': 'application/x-www-form-urlencoded'}, headers: {'Content-Type': 'application/x-www-form-urlencoded'},
body: new URLSearchParams({ body: new URLSearchParams({
action: 'zeitfresser_optimize_images', action: 'zeitfresser_optimize_images',
nonce: '<?php echo wp_create_nonce('zeitfresser_performance_tools'); ?>' nonce: '<?php echo wp_create_nonce('zeitfresser_image_optimizer'); ?>'
}) })
}) })
.then(res => res.json()) .then(res => res.json())
@@ -224,3 +224,11 @@ if ( ! function_exists( 'zeitfresser_asset' ) ) {
return get_template_directory_uri() . '/assets/' . ltrim($path, '/'); return get_template_directory_uri() . '/assets/' . ltrim($path, '/');
} }
} }
/**
* Delete Cookie Button
*/
add_filter( 'comment_form_default_fields', function( $fields ) {
unset( $fields['cookies'] );
return $fields;
});
@@ -9,6 +9,31 @@ if ( ! defined( 'ABSPATH' ) ) {
exit; exit;
} }
/**
* Enqueue TOC script when needed.
*/
function zeitfresser_enqueue_toc_assets() {
if ( ! is_singular( 'post' ) ) {
return;
}
if ( ! zeitfresser_has_floating_toc() ) {
return;
}
$asset = zeitfresser_asset_versioned('/js/toc.js');
wp_enqueue_script(
'zeitfresser-toc',
$asset['url'],
[],
$asset['version'],
true
);
}
add_action( 'wp_enqueue_scripts', 'zeitfresser_enqueue_toc_assets', 20 );
/** /**
* Return whether article TOC output is enabled. * Return whether article TOC output is enabled.
* *
+2 -4
View File
@@ -1,6 +1,6 @@
<p align="center"> <p align="center">
<a href="https://ztfr.eu/matrix"> <a href="https://ztfr.eu/matrix">
<img src="assets/community-badge.png" alt="Join Zeitfresser Matrix Community" height="70" /> <img src="screenshot.png" alt="Join Zeitfresser Matrix Community" height="70" />
</a> </a>
</p> </p>
@@ -130,9 +130,7 @@ Using caching, a CDN, and optimized hosting will further improve performance, es
## 🛠 Development & Support ## 🛠 Development & Support
If you want to get support or participate in development, you can join the If you want to get support or participate in development, you can join the <a href="https://ztfr.eu/matrix">Zeitfresser Matrix Community</a> or the <a href="https://look.ztfr.eu/#/#support:ztfr.eu">Development & Support Channel</a>.
<a href="https://ztfr.eu/matrix">Zeitfresser Matrix Community</a> or the
<a href="https://look.ztfr.eu/#/#support:ztfr.eu">Development & Support Channel</a>.
## 📄 License ## 📄 License
+2
View File
@@ -18,6 +18,8 @@ $show_hide_related_posts = get_theme_mod(
<div class="container"> <div class="container">
<div class="main-wrapper"> <div class="main-wrapper">
<?php zeitfresser_render_floating_toc(); ?>
<section class="page-section full-width-view"> <section class="page-section full-width-view">
<div class="detail-content"> <div class="detail-content">
+2232 -4
View File
File diff suppressed because one or more lines are too long