Eigene Plugins

Zur Bereitstellung weiterer Funktionalität innerhalb der Website wurden zwei WordPress-Plugins konfiguriert:

  • „Alle Kommentare“ stellt einen Short Code-Eintrag zur Verfügung, der die Ausgabe aller Kommentare in der Website auf einer Seite ermöglicht.
  • „user-display-settings“ erlaubt es dem Benuzter, die Schriftröße anzupassen und die Hervorhebung von relevanten Textstellen im Seitentext ein- oder auszuschalten.
  • „page-list-display“ verbessert das Erscheinungsbild des Seitenverzeichnisses, das durch das PlugIn „page-list“ erzeugt wird. Das Verzeichnis erhält eine aufklappbare Strukur, Seiten mit und ohne Unterseiten werden durch entsprechende Symbole gekennzeichnet. Für Seiten mit Unterseiten kann die Liste der Unterseiten durch Klick auf das Symbol geöffnet oder geschlossen werden. Der Status der Listenanzeige wird lokal persistiert, so dass er beim nächsten Besuch des Seitenverzeichnisses wieder zur Verfügung steht.
  • „reading-paths“ umfasst die Administration und Anwendung von Lesepfaden, also einer Abfolge von Seiten, die sinnvoll in dieser Reihenfolge gelesen werden können. Die Lesepfade werden mit einer Administrationsfunktion erstellt und gepflegt. Dem Anwender werden die vorhandenen Lesepfade zur Auswahl gestellt. Die geöffneten Lesepfade werden dem Benutzer zum Weiterlesen bereitgestellt. Die im Rahmen eines Lesepfades bereitgestellten Seiten werden durch Navigations- und Verwaltungsfunktionen ergänzt.

Die erforderlichen Dateien werden in je eigenen Verzeichnissen im Verzeichnis ./wp-content/plugins abgelegt. Von hieraus werden die Plugins dann automatisch geladen und stehen zur Nutzung bereit.

Der Code der erforderlichen Dateien wurde auf Anfrage durch ChatGTP erstellt.

Alle Kommentare

Das Plugin umfasst zwei Dateien:

alle-kommentare.php
<?php
/**
 * Plugin Name: Alle Kommentare
 * Description: Öffentliche Seite mit allen freigegebenen Kommentaren inkl. Links.
 * Version: 1.0
 * Author: —
 */

add_shortcode('alle_kommentare', 'uds_shortcode_alle_kommentare');

function uds_shortcode_alle_kommentare($atts) {

    $atts = shortcode_atts([
        'pro_seite' => 20,
        'seite'     => max(1, get_query_var('paged') ?: 1),
    ], $atts, 'alle_kommentare');

    $offset = ($atts['seite'] - 1) * $atts['pro_seite'];

    $comments = get_comments([
        'status'  => 'approve',
        'number'  => (int)$atts['pro_seite'],
        'offset'  => (int)$offset,
        'orderby' => 'comment_date_gmt',
        'order'   => 'DESC',
    ]);

    if (!$comments) {
        return '<p>Keine Kommentare vorhanden.</p>';
    }

    $total_comments = get_comments([
        'status' => 'approve',
        'count'  => true,
    ]);

    $total_pages = ceil($total_comments / $atts['pro_seite']);

    ob_start();

    echo '<div class="alle-kommentare"><ul>';

    foreach ($comments as $c) {
        echo '<li>';
        echo '<strong>' . esc_html($c->comment_author) . '</strong> ';
        echo '<span>(' . esc_html(get_comment_date('', $c)) . ')</span><br>';
        echo esc_html(wp_trim_words($c->comment_content, 30)) . '<br>';
        echo '<a href="' . esc_url(get_comment_link($c)) . '">';
        echo esc_html(get_the_title($c->comment_post_ID));
        echo '</a>';
        echo '</li>';
    }

    echo '</ul>';

    if ($total_pages > 1) {
        echo '<nav>';
        for ($i = 1; $i <= $total_pages; $i++) {
            echo $i == $atts['seite']
                ? '<strong>' . $i . '</strong> '
                : '<a href="' . esc_url(add_query_arg('paged', $i)) . '">' . $i . '</a> ';
        }
        echo '</nav>';
    }

    echo '</div>';

    return ob_get_clean();
}
alle-kommentare.css
.alle-kommentare ul {
  list-style: none;
  padding: 0;
}

.alle-kommentare li {
  margin-bottom: 1.2em;
  padding-bottom: 0.8em;
  border-bottom: 1px solid #ddd;
}

.kommentar-pagination a {
  margin-right: 0.5em;
}

User Display Settings

Das Plugin umfasst drei Dateien:

user-display-settings.php
// <?php
// /**
 // * Plugin Name: User Display Settings
 // * Description: Anzeigeoptionen (Schriftgröße, Hervorhebung, Seitenanfang-Button)
 // * Version: 1.3
 // * Author: —
 // */

// add_action('wp_enqueue_scripts', 'uds_enqueue_assets', 1);
// function uds_enqueue_assets() {

    // wp_enqueue_style(
        // 'uds-display-css',
        // plugin_dir_url(__FILE__) . 'user-display.css',
        // array(),
        // filemtime(__DIR__ . '/user-display.css')
    // );

    // wp_enqueue_script(
        // 'uds-display-js',
        // plugin_dir_url(__FILE__) . 'user-display.js',
        // array(),
        // filemtime(__DIR__ . '/user-display.js'),
        // false
    // );
// }

// add_action('wp_footer', 'uds_inject_display_menu');
// function uds_inject_display_menu() {

    // echo '<div id="uds-ui">'
       // . '<button id="uds-toggle" type="button" aria-expanded="false">Anzeige</button>'
       // . '<div id="uds-menu" hidden>'
       // . '<button type="button" data-font="-10">Text kleiner</button>'
       // . '<button type="button" data-font="10">Text größer</button>'
       // . '<label>'
       // . '<input type="checkbox" id="uds-important-toggle"> wichtige Begriffe hervorheben'
       // . '</label>'
       // . '</div>'
       // . '</div>'

       // /* Seitenanfang-Button */
       // . '<button id="uds-scroll-top" type="button" aria-label="Zum Seitenanfang">▲</button>';
// }
user-display.css
/* Allgemeine Formatierungen*/
#content {
	width: 60em; /* Begrenzungs der Seitenbreite zur besseren Lesbakeit auf breiten Bildschirmen */
	font-size: larger; /* Text im Standard größer */
}

/* Überschriften */
h1.wp-block-heading, /* Überschriften werden mit normaler Schriftstärke dargestellt. +/
h2.wp-block-heading,
h3.wp-block-heading,
h4.wp-block-heading,
h5.wp-block-heading,
h6.wp-block-heading {
  font-weight: normal;
}

/* Quellenverzeichnis – Absätze /* Quelleneinträge mit hängendem Einzug */
.quellenverzeichnis p {
  text-indent: -1.5em;
  margin-left: 1.5em;
  margin-top: 0.6em;
  margin-bottom: 0.2em;
}

/* Details-Block bündig mit den hängenden Zeilen */
.quellenverzeichnis details {
  margin: 0 0 0.6em 1.5em;   /* gleiches Einrücken wie die Folgezeilen, kein Extra-Abstand oben */
  padding-left: 0.5em;
}

details /* Aufklappbare Texte */
{
  border-left: 2px solid #ccc;
  background: #fafafa;
}
/* Summary schlicht, mit Kapitälchen statt Fett */
details summary {
  font-variant: small-caps;
  font-weight: normal;
  cursor: pointer;
  margin: 0;
}
/* Absätze im Inhaltsblock ohne zusätzliche Abstände */
details p {
  margin: 0;
  text-indent: 0;
}

/* Minimaler Abstand nur zwischen Inhalt- und Beitrag-Absatz */
details p + p {
  margin-top: 0.2em;
}

/* Menü */
.navbar-menu {
	width: 100
}

/* Kennzeichnung von Gesprächsteilnehmnern in Chats (Ich:, ChatGPT:) */
span.interlocutor {
    font-weight: bold;
}
/* Tabellenausrichtung */
table {
	text-align: left;
	vertical-align: top;
}
user-display.js
document.addEventListener('DOMContentLoaded', function () {

  var root = document.documentElement;
  if (!root) return;

  /* ==========================================
     Zustand
  ========================================== */
  function applyFontSize(value) {
    root.style.setProperty('--base-font-size', value + '
  }

  function applyImportant(enabled) {
    root.classList.toggle('important-on', enabled);
  }

  /* ==========================================
     Persistenz
  ========================================== */
  var storedFontSize = localStorage.getItem('fontSize');
  var importantOn   = localStorage.getItem('importantEnabled') === '1';

  if (storedFontSize) {
    applyFontSize(parseInt(storedFontSize, 10));
  }

  applyImportant(importantOn);

  /* ==========================================
     UI
  ========================================== */
  var ui = document.getElementById('uds-ui');
  var menu = document.getElementById('uds-menu');
  var toggleBtn = document.getElementById('uds-toggle');
  var importantCb = document.getElementById('uds-important-toggle');
  var scrollTopBtn = document.getElementById('uds-scroll-top');

  if (!ui) return;

  /* Menü */
  toggleBtn.addEventListener('click', function () {
    var open = menu.hasAttribute('hidden');
    menu.toggleAttribute('hidden');
    toggleBtn.setAttribute('aria-expanded', open ? 'true' : 'false');
  });

  /* Schriftgröße */
  var fontButtons = ui.querySelectorAll('[data-font]');
  for (var i = 0; i < fontButtons.length; i++) {
    fontButtons[i].addEventListener('click', function () {
      var current = parseInt(localStorage.getItem('fontSize'), 10) || 100;
      var delta   = parseInt(this.getAttribute('data-font'), 10);
      var next    = Math.min(150, Math.max(80, current + delta));

      applyFontSize(next);
      localStorage.setItem('fontSize', next);
    });
  }

  /* Important Terms */
  if (importantCb) {
    importantCb.checked = importantOn;

    importantCb.addEventListener('change', function () {
      applyImportant(this.checked);
      localStorage.setItem('importantEnabled', this.checked ? '1' : '0');
    });
  }

  /* ==========================================
     Scroll to Top
  ========================================== */
  if (scrollTopBtn) {

    scrollTopBtn.addEventListener('click', function () {
      window.scrollTo({
        top: 0,
        behavior: 'smooth'
      });
    });

    /* Sichtbarkeit steuern */
    window.addEventListener('scroll', function () {
      if (window.scrollY > 400) {
        scrollTopBtn.classList.add('visible');
      } else {
        scrollTopBtn.classList.remove('visible');
      }
    });
  }

});

Page List Display

Dieses Plugin umfasst drei Dateien:

page-list-display.php
<?php
/*
Plugin Name: Page List Display
Description: Expandable tree navigation for Gutenberg page lists.
Version: 1.0
*/

function page_list_display_enqueue() {

    wp_enqueue_script(
        'page-list-display-js',
        plugin_dir_url(__FILE__) . 'page-list-display.js',
        array(),
        null,
        true
    );

    wp_enqueue_style(
        'page-list-display-css',
        plugin_dir_url(__FILE__) . 'page-list-display.css'
    );

}

add_action('wp_enqueue_scripts', 'page_list_display_enqueue');
page-list-display.js
document.addEventListener("DOMContentLoaded", function(){

const tree = document.querySelector(".wp-block-page-list");

if(!tree) return;

const STORAGE_KEY = "pageTreeState";


/* ------------------------------------------------ */
/* gespeicherten Zustand laden                      */
/* ------------------------------------------------ */

let savedState = [];

try{

    const stored = localStorage.getItem(STORAGE_KEY);

    if(stored){
        savedState = JSON.parse(stored);
    }

}catch(e){}


/* ------------------------------------------------ */
/* Toggle für alle Knoten erzeugen                  */
/* ------------------------------------------------ */

const nodes = tree.querySelectorAll("li");

nodes.forEach(function(li){

    const link = li.querySelector(":scope > a");
    const submenu = li.querySelector(":scope > ul");

    if(!submenu){
        return;
    }

    const toggle = document.createElement("span");
    toggle.className = "page-tree-toggle";

    if(savedState.includes(link.href)){
        li.classList.add("tree-open");
        toggle.textContent = "▾";
    }else{
        toggle.textContent = "▸";
    }

    li.insertBefore(toggle, link);

    toggle.addEventListener("click", function(e){

        e.preventDefault();
        e.stopPropagation();

        li.classList.toggle("tree-open");

        toggle.textContent =
            li.classList.contains("tree-open") ? "▾" : "▸";

        saveState();

    });

});


/* ------------------------------------------------ */
/* aktuellen Navigationspfad öffnen                 */
/* ------------------------------------------------ */

tree.querySelectorAll(
".current-menu-item, .current_page_parent, .current_page_ancestor"
).forEach(function(node){

    let li = node.closest("li");

    while(li){

        li.classList.add("tree-open");

        const toggle = li.querySelector(":scope > .page-tree-toggle");

        if(toggle){
            toggle.textContent = "▾";
        }

        li = li.parentElement.closest("li");

    }

});


/* ------------------------------------------------ */
/* Zustand speichern                                */
/* ------------------------------------------------ */

function saveState(){

    const openNodes = [];

    tree.querySelectorAll("li.tree-open").forEach(function(li){

        const link = li.querySelector(":scope > a");

        if(link){
            openNodes.push(link.href);
        }

    });

    localStorage.setItem(
        STORAGE_KEY,
        JSON.stringify(openNodes)
    );

}

});
page-list-display.css
/* ================================================================ */
/* Entfernen der Standard-Listenmarker                              */
/* ================================================================ */

.wp-block-page-list,
.wp-block-page-list ul,
.wp-block-page-list li{
    list-style:none !important;
}

.wp-block-page-list li::marker{
    content:"" !important;
}


/* ================================================================ */
/* Entfernen von Theme-Pseudoelementen                              */
/* ================================================================ */

.wp-block-page-list a::before,
.wp-block-pages-list__item__link::before{
    content:none !important;
    display:none !important;
}


/* ================================================================ */
/* Einheitliche Symbolbox                                           */
/* ================================================================ */

.page-tree-toggle,
.wp-block-page-list li:not(.has-child)::before{

    display:inline-block;
    width:1.2em;
    margin-right:6px;
    text-align:center;

}


/* ================================================================ */
/* Toggle-Symbol (interaktive Knoten)                               */
/* ================================================================ */

.page-tree-toggle{

    cursor:pointer;
    user-select:none;
    font-weight:bold;

}

.page-tree-toggle:hover{
    transform:scale(1.1);
}


/* ================================================================ */
/* Symbol für Seiten ohne Unterseiten                               */
/* ================================================================ */

.wp-block-page-list li:not(.has-child)::before{

    content:"•";
    font-size:1em;
    color:inherit;

}


/* ================================================================ */
/* Unterlistensteuerung                                             */
/* ================================================================ */

.wp-block-page-list .wp-block-navigation__submenu-container{
    display:none;
}

.wp-block-page-list li.tree-open > .wp-block-navigation__submenu-container{
    display:block;
}


/* ================================================================ */
/* Gleichmäßige Einrückung                                          */
/* ================================================================ */

.wp-block-page-list ul{
    margin-left:1.2em;
}


/* ================================================================ */
/* Explorer-Baumlinien                                              */
/* ================================================================ */

.wp-block-page-list ul{

    position:relative;
    padding-left:0.6em;

}

.wp-block-page-list ul::before{

    content:"";
    position:absolute;

    top:0;
    bottom:0;
    left:0;

    border-left:1px solid #ddd;

}

.wp-block-page-list li{

    position:relative;
    margin:2px 0;

}

.wp-block-page-list li::after{

    content:"";
    position:absolute;

    top:0.8em;
    left:-0.6em;

    width:0.6em;

    border-top:1px solid #ddd;

}


/* ================================================================ */
/* Letztes Element sauber abschließen                               */
/* ================================================================ */

.wp-block-page-list li:last-child::before{
    background:white;
}

Reading Paths

Dieses Plugin umfasst acht Dateien:

reading_paths.php
<?php
/*
Plugin Name: Reading Paths
Description: Lesepfade für WordPress-Seiten
Version: 2.0
*/

if (!defined('ABSPATH')) exit;


/* --------------------------------------------------
   Konstanten
-------------------------------------------------- */

define('RP_PATH', plugin_dir_path(__FILE__));
define('RP_URL', plugin_dir_url(__FILE__));


/* --------------------------------------------------
   Plugin-Dateien laden
-------------------------------------------------- */

require_once RP_PATH.'admin-reading-paths.php';
require_once RP_PATH.'progress-tracker.php';
require_once RP_PATH.'frontend-shortcodes.php';
require_once RP_PATH.'navigation-box.php';


/* --------------------------------------------------
   Custom Post Type
-------------------------------------------------- */

add_action('init','rp_register_post_type');

function rp_register_post_type(){

register_post_type('reading_path',[
'label'=>'Lesepfade',
'public'=>false,
'show_ui'=>true,
'show_in_menu'=>true,
'menu_position'=>25,

'supports'=>[
'title',
'editor'
],

'capability_type'=>'post'
]);

}


/* --------------------------------------------------
   Frontend CSS
-------------------------------------------------- */

add_action('wp_enqueue_scripts','rp_frontend_assets');

function rp_frontend_assets(){

wp_enqueue_style(
'rp-css',
RP_URL.'assets/reading-paths.css',
[],
filemtime(RP_PATH.'assets/reading-paths.css')
);

}


/* --------------------------------------------------
   Admin Assets
-------------------------------------------------- */

add_action('admin_enqueue_scripts','rp_admin_assets');

function rp_admin_assets($hook){

$screen = get_current_screen();

if(!$screen) return;

/* nur im Lesepfad-Editor laden */

if($screen->post_type !== 'reading_path') return;


/* CSS */

wp_enqueue_style(
'rp-admin-css',
RP_URL.'assets/admin-reading-paths.css',
[],
filemtime(RP_PATH.'assets/admin-reading-paths.css')
);


/* jQuery UI */

wp_enqueue_script('jquery-ui-sortable');
wp_enqueue_script('jquery-ui-draggable');
wp_enqueue_script('jquery-ui-droppable');


/* JS */

wp_enqueue_script(
'rp-admin-js',
RP_URL.'assets/admin-reading-paths.js',
[
'jquery',
'jquery-ui-sortable',
'jquery-ui-draggable',
'jquery-ui-droppable'
],
filemtime(RP_PATH.'assets/admin-reading-paths.js'),
true
);

}


/* --------------------------------------------------
   URL Aktionen
-------------------------------------------------- */

add_action('init','rp_handle_actions');

function rp_handle_actions(){

if(!is_user_logged_in()) return;


/* Lesepfad starten */

if(isset($_GET['rp_path'])){

rp_start_path((int)$_GET['rp_path']);

}


/* Lesepfad schließen */

if(isset($_GET['rp_close'])){

rp_close_path(
(int)$_GET['rp_close'],
isset($_GET['rp_read'])
);

}

}


/* --------------------------------------------------
   Shortcodes sicher ausführen
-------------------------------------------------- */

add_filter('the_content','do_shortcode',11);
admin-reading-paths.php
<?php

if (!defined('ABSPATH')) exit;


/* --------------------------------------------------
   Metabox registrieren
-------------------------------------------------- */

add_action('add_meta_boxes','rp_add_editor_box');

function rp_add_editor_box(){

add_meta_box(
'rp_editor',
'Lesepfad-Editor',
'rp_editor_html',
'reading_path',
'normal',
'high'
);

}


/* --------------------------------------------------
   Seitenhierarchie erzeugen
-------------------------------------------------- */

function rp_build_tree($pages,$parent=0,$level=0){

$out=[];

foreach($pages as $p){

if($p->post_parent==$parent){

$p->rp_level=$level;

$out[]=$p;

$out=array_merge(
$out,
rp_build_tree($pages,$p->ID,$level+1)
);

}

}

return $out;

}


/* --------------------------------------------------
   Metabox Inhalt
-------------------------------------------------- */

function rp_editor_html($post){

$selected = get_post_meta($post->ID,'reading_path_pages',true);

if(!is_array($selected)){
$selected=[];
}


/* Seiten laden */

$pages=get_pages([
'sort_column'=>'menu_order'
]);

$pages=rp_build_tree($pages);

?>


<div class="rp-editor">


<!-- linke Liste -->

<div class="rp-column">

<h4>Alle Seiten</h4>

<ul id="rp-pages-source">

<?php foreach($pages as $p): ?>

<li data-id="<?php echo $p->ID;?>">

<span class="rp-indent" style="margin-left:<?php echo $p->rp_level*18;?>px"></span>

<?php echo esc_html($p->post_title);?>

</li>

<?php endforeach; ?>

</ul>

</div>


<!-- rechte Liste -->

<div class="rp-column">

<h4>Lesepfad</h4>

<ul id="rp-pages-target">

<?php

foreach($selected as $i=>$id){

$page=get_post($id);

if(!$page) continue;

?>

<li data-id="<?php echo $id;?>">

<span class="rp-number"><?php echo $i+1;?>.</span>

<span class="rp-title"><?php echo esc_html($page->post_title);?></span>

<button type="button" class="rp-remove">×</button>

<input type="hidden" name="reading_path_pages[]" value="<?php echo $id;?>">

</li>

<?php

}

?>

</ul>

</div>


</div>


<?php

}


/* --------------------------------------------------
   speichern
-------------------------------------------------- */

add_action('save_post','rp_save_path');

function rp_save_path($post_id){

if(get_post_type($post_id)!=='reading_path') return;

if(!isset($_POST['reading_path_pages'])){

delete_post_meta($post_id,'reading_path_pages');

return;

}

$pages=array_map('intval',$_POST['reading_path_pages']);

update_post_meta($post_id,'reading_path_pages',$pages);

}
frontend-shortcodes.php
<?php

if (!defined('ABSPATH')) exit;

add_action('init','rp_register_shortcodes');

function rp_register_shortcodes(){

add_shortcode('all_readingpaths','rp_shortcode_all_paths');
add_shortcode('my_readingpaths','rp_shortcode_my_paths');

}


/* alle Lesepfade */

function rp_shortcode_all_paths(){

if(!is_user_logged_in()) return '';

$paths = get_posts([
'post_type'=>'reading_path',
'numberposts'=>-1,
'orderby'=>'title',
'order'=>'ASC'
]);

$data = rp_get_user_paths();

$out='<div class="readingpaths-heading">Alle Lesepfade</div>';

foreach($paths as $path){

$pages = get_post_meta($path->ID,'reading_path_pages',true);

if(!$pages) continue;

$first_page = get_permalink($pages[0]);

$url = add_query_arg('rp_path',$path->ID,$first_page);

$read = !empty($data[$path->ID]['read']);

$out.='<div class="rp-path-row">';
$out.='<a href="'.esc_url($url).'">'.$path->post_title.'</a>';

if($read) $out.=' ✓';

$out.='</div>';

}

return $out;

}


/* eigene Lesepfade */

function rp_shortcode_my_paths(){

if(!is_user_logged_in()) return '';

$active = rp_get_active_paths();

$out='<div class="readingpaths-heading">Meine Lesepfade</div>';

foreach($active as $path_id=>$info){

$pages = get_post_meta($path_id,'reading_path_pages',true);

if(!$pages) continue;

$pos = $info['position'] ?? 0;

$url = add_query_arg(
'rp_path',
$path_id,
get_permalink($pages[$pos])
);

$out.='<div class="rp-path-row">';
$out.='<a href="'.esc_url($url).'">'.get_the_title($path_id).'</a>';
$out.='</div>';

}

return $out;

}
navigation-box.php
<?php

if (!defined('ABSPATH')) exit;


/* Navigationbox einfügen */

add_filter('the_content','rp_navigation_box');

function rp_navigation_box($content){

if(!is_user_logged_in()) return $content;

$path_id = rp_get_current_path();

if(!$path_id) return $content;

$pages = get_post_meta($path_id,'reading_path_pages',true);

if(!$pages || !is_array($pages)) return $content;

$page_id = get_the_ID();

$index = array_search($page_id,$pages);

if($index===false) return $content;

$total = count($pages);

rp_update_position($path_id,$index);

$title = get_the_title($path_id);


/* Hilfsfunktion erzeugt URL mit Lesepfad-Kontext */

function rp_path_url($page_id,$path_id){

$url = get_permalink($page_id);

return add_query_arg('rp_path',$path_id,$url);

}


$first = rp_path_url($pages[0],$path_id);

$prev = $index>0 ? rp_path_url($pages[$index-1],$path_id) : null;

$next = $index<$total-1 ? rp_path_url($pages[$index+1],$path_id) : null;


ob_start();
?>

<div class="rp-navbox">

<div class="rp-nav-title">
<?php echo esc_html($title); ?>
(<?php echo $index+1;?> / <?php echo $total;?>)
</div>

<div class="rp-nav-buttons">

<a href="<?php echo esc_url($first); ?>">|&lt;</a>

<?php if($prev): ?>
<a href="<?php echo esc_url($prev); ?>">&lt;</a>
<?php endif; ?>

<?php if($next): ?>
<a href="<?php echo esc_url($next); ?>">&gt;</a>
<?php endif; ?>

<a href="<?php echo esc_url(add_query_arg('rp_close',$path_id)); ?>">
schließen
</a>

<a href="<?php echo esc_url(add_query_arg([
'rp_close'=>$path_id,
'rp_read'=>1
])); ?>">
gelesen schließen
</a>

</div>

</div>

<?php

return ob_get_clean().$content;

}


/* ---------------------------------------------------
   Lesepfad-Kontext automatisch an interne Links anhängen
--------------------------------------------------- */

add_filter('page_link','rp_add_path_to_link',10,2);
add_filter('post_link','rp_add_path_to_link',10,2);

function rp_add_path_to_link($url,$post){

if(!is_user_logged_in()) return $url;

$path_id = rp_get_current_path();

if(!$path_id) return $url;


/* nur interne Links modifizieren */

$home = home_url();

if(strpos($url,$home)!==0){
return $url;
}


/* Parameter anhängen */

return add_query_arg('rp_path',$path_id,$url);

}
progress-tracker.php
<?php

if (!defined('ABSPATH')) exit;


/* Userdaten laden */

function rp_get_user_paths(){

    $data = get_user_meta(get_current_user_id(),'rp_user_paths',true);

    if(!is_array($data)){
        $data=[];
    }

    return $data;

}


/* speichern */

function rp_save_user_paths($data){

    update_user_meta(get_current_user_id(),'rp_user_paths',$data);

}


/* aktuellen Lesepfad setzen */

function rp_set_current_path($path_id){

    update_user_meta(get_current_user_id(),'rp_current_path',$path_id);

}

function rp_get_current_path(){

    return get_user_meta(get_current_user_id(),'rp_current_path',true);

}

function rp_clear_current_path(){

    delete_user_meta(get_current_user_id(),'rp_current_path');

}


/* Lesepfad starten */

function rp_start_path($path_id){

    $data = rp_get_user_paths();

    /* Lesepfad aktivieren */

    if(!isset($data[$path_id])){
        $data[$path_id]=[];
    }

    $data[$path_id]['active']=true;

    if(!isset($data[$path_id]['position'])){
        $data[$path_id]['position']=0;
    }

    rp_save_user_paths($data);

    /* aktuellen Lesepfad setzen */

    rp_set_current_path($path_id);

}


/* Position aktualisieren */

function rp_update_position($path_id,$index){

    $data = rp_get_user_paths();

    if(!isset($data[$path_id])){
        $data[$path_id]=[];
    }

    $data[$path_id]['position']=$index;

    rp_save_user_paths($data);

}


/* Lesepfad schließen */

function rp_close_path($path_id,$read=false){

    $data = rp_get_user_paths();

    if(isset($data[$path_id]['active'])){
        unset($data[$path_id]['active']);
    }

    if($read){
        $data[$path_id]['read']=true;
    }

    rp_save_user_paths($data);

    if(rp_get_current_path()==$path_id){
        rp_clear_current_path();
    }

}


/* aktive Lesepfade */

function rp_get_active_paths(){

    $data = rp_get_user_paths();

    $active=[];

    foreach($data as $path_id=>$info){

        if(!empty($info['active'])){
            $active[$path_id]=$info;
        }

    }

    return $active;

}
assets/admin-reading-paths.css
.rp-editor{
display:flex;
gap:30px;
margin-top:10px;
}

/* Spalten */

.rp-column{
width:50
}

/* Listen */

#rp-pages-source,
#rp-pages-target{

border:1px solid #ccc;
list-style:none;
padding:8px;
margin:0;

height:420px;
overflow-y:auto;

background:#fff;

}


/* Seitenliste */

#rp-pages-source li{
padding:4px 6px;
cursor:grab;
}


/* Lesepfadliste */

#rp-pages-target li{

display:flex;
align-items:center;
gap:8px;

padding:4px 6px;
cursor:move;

border-bottom:1px solid #eee;

}


/* Nummer */

.rp-number{
width:32px;
text-align:right;
font-weight:600;
color:#555;
}


/* Titel */

.rp-title{
flex:1;
}


/* Löschen */

.rp-remove{

border:none;
background:none;
cursor:pointer;

font-size:16px;
line-height:1;

color:#666;

}

.rp-remove:hover{
color:#000;
}


/* Drag placeholder */

.rp-placeholder{

background:#f3f3f3;
border:1px dashed #bbb;
height:28px;

}

.rp-duplicate{
outline:2px solid #d63638;
}
assets/admin-reading-paths.js
jQuery(function($){

function renumber(){

$('#rp-pages-target li').each(function(i){

$(this).find('.rp-number').text((i+1)+'.');

});

}


/* Sortierung innerhalb der Ziel-Liste */

$('#rp-pages-target').sortable({

placeholder:'rp-placeholder',

update:function(){
renumber();
}

});


/* Drag aus Seitenliste */

$('#rp-pages-source li').draggable({

helper:'clone',
revert:'invalid'

});


/* Drop in Lesepfad */

$('#rp-pages-target').droppable({

accept:'#rp-pages-source li',

drop:function(event,ui){

let id = ui.draggable.data('id');
let title = ui.draggable.text().trim();


/* prüfen auf Duplikat */

if($('#rp-pages-target li[data-id="'+id+'"]').length>0){
return;
}


/* neues Element erzeugen */

let item=$('<li data-id="'+id+'">'+
'<span class="rp-number"></span>'+
'<span class="rp-title">'+title+'</span>'+
'<button type="button" class="rp-remove">×</button>'+
'<input type="hidden" name="reading_path_pages[]" value="'+id+'">'+
'</li>');


/* -------------------------------------------------
   Position anhand Mauskoordinate bestimmen
------------------------------------------------- */

let inserted=false;

$('#rp-pages-target li').each(function(){

let offset=$(this).offset();
let height=$(this).outerHeight();

let middle=offset.top + height/2;

if(event.pageY < middle){

item.insertBefore($(this));
inserted=true;

return false;

}

});


/* falls keine Position gefunden → ans Ende */

if(!inserted){

$('#rp-pages-target').append(item);

}


renumber();

}

});


/* löschen */

$(document).on('click','.rp-remove',function(){

$(this).closest('li').remove();

renumber();

});

});
assets/reading-paths.css
.readingpaths-heading{
font-variant:small-caps;
font-weight:normal;
margin-bottom:10px;
}

.rp-path-row{
margin:6px 0;
}

.rp-navbox{
border:1px solid #ccc;
padding:10px;
margin-bottom:20px;
display:flex;
flex-direction:column;
gap:6px;
}

.rp-nav-title{
font-weight:bold;
}

.rp-nav-buttons{
display:flex;
flex-wrap:wrap;
gap:10px;
}

.rp-nav-buttons a{
text-decoration:none;
padding:4px 8px;
border:1px solid #ccc;
}
Schreibe einen Kommentar0