Pith - miter
miter/front/pith.php [11.0 kb]
Modified: 17:48:02 114 026 (11 Jul 026)
14 Days Ago
<?php
    $folderPath = isset($_GET['p']) ? $_GET['p'] : null;
    $rawFile = isset($_GET['file']) ? $_GET['file'] : null;
    $rootPath = realpath('piths/source'); 

    $filePath = ($folderPath && $rawFile) ? $folderPath . DIRECTORY_SEPARATOR . $rawFile : $rawFile;

    // "hide" source/.htaccess
    if ($filePath && basename($filePath) === '.htaccess') {
        $filePath = null;
        $rawFile = null;
    }

    // ?p=
    $topFolder = $folderPath ?: basename($rootPath);
    if (!$folderPath && $filePath) {
        $partsPath = explode(DIRECTORY_SEPARATOR, ltrim($filePath, DIRECTORY_SEPARATOR));
        $topFolder = count($partsPath) > 1 ? $partsPath[0] : basename($rootPath);
    }

    // ?p=[folder]?file=README.md
    if ($folderPath && !$rawFile) {
        $readmePath = $folderPath . DIRECTORY_SEPARATOR . 'README.md';
        if (is_file($rootPath . DIRECTORY_SEPARATOR . $readmePath)) {
            $filePath = $readmePath;
        }
    }

    // download button
    if (isset($_GET['download']) && $filePath) {
        $safePath = getSafePath($rootPath . DIRECTORY_SEPARATOR . $filePath, $rootPath);
        if ($safePath && is_file($safePath)) {
            header('Content-Description: File Transfer');
            header('Content-Type: application/octet-stream');
            header('Content-Disposition: attachment; filename="' . basename($safePath) . '"');
            header('Expires: 0');
            header('Cache-Control: must-revalidate');
            header('Pragma: public');
            header('Content-Length: ' . filesize($safePath));
            readfile($safePath);
            exit;
        }
    }

    function getSafePath($path, $root) {
        $realPath = realpath($path);
        if ($realPath && strpos($realPath . DIRECTORY_SEPARATOR, $root . DIRECTORY_SEPARATOR) === 0) {
            return $realPath;
        }
        return null;
    }

    // tree build
    function renderTree($dir, $root, $filePath = null, $folderPath = null, $isRoot = true) {
        $items = scandir($dir);
        echo "<ul>";
        foreach ($items as $item) {
            if ($item == "." || $item == ".." || strpos($item, '.') === 0) continue;

            $fullPath = $dir . DIRECTORY_SEPARATOR . $item;
            $relPath = str_replace($root . DIRECTORY_SEPARATOR, '', $fullPath);

            if ($folderPath) {
                if ($relPath !== $folderPath && strpos($relPath, $folderPath . DIRECTORY_SEPARATOR) !== 0 && strpos($folderPath, $relPath . DIRECTORY_SEPARATOR) !== 0) {
                    continue;
                }
            }
            
            if (is_dir($fullPath)) {
            $isOpen = "";
            if ($folderPath && ($relPath === $folderPath || strpos($relPath, $folderPath . DIRECTORY_SEPARATOR) === 0 || strpos($folderPath, $relPath . DIRECTORY_SEPARATOR) === 0)) {
                $isOpen = "open";
            }

            echo "<li class='folder'><details $isOpen data-path='" . htmlspecialchars($relPath) . "'><summary>" . htmlspecialchars($item) . "</summary>";

                renderTree($fullPath, $root, $filePath, $folderPath, false);
                echo "</details></li>";
            } else {
                $activeClass = ($filePath === $relPath) ? "active-file" : "";
                $parts = explode(DIRECTORY_SEPARATOR, $relPath);
                $fileTopFolder = count($parts) > 1 ? $parts[0] : null;

                if ($fileTopFolder) {
                    $urlFile = substr($relPath, strlen($fileTopFolder) + 1);
                    $url = "?p=" . urlencode($fileTopFolder) . "&file=" . urlencode($urlFile);
                } else {
                    $url = "?p=&file=" . urlencode($relPath);
                }
                echo "<li class='file $activeClass'><a href='$url'>" . htmlspecialchars($item) . "</a></li>";
            }
        }
        echo "</ul>";
    }

    function get_otc_date($timestamp = 'now', $tz_input = null) {
        $tz_input = $tz_input ?? ($_GET['tz'] ?? 'UTC');
        $timezone = in_array($tz_input, timezone_identifiers_list())
            ? new DateTimeZone($tz_input)
            : new DateTimeZone('UTC');
    
        $dt = ($timestamp === 'now') 
            ? new DateTimeImmutable('now', $timezone) 
            : (new DateTimeImmutable('@' . $timestamp))->setTimezone($timezone);
    
        $year = (int) $dt->format('Y');
        $hour = $dt->format('H');
        $min  = $dt->format('i');
        $sec  = $dt->format('s');
    
        $new_year = new DateTimeImmutable("{$year}-03-20", $timezone);
        if ($dt < $new_year) {
            $year--;
            $new_year = new DateTimeImmutable("{$year}-03-20", $timezone);
        }
    
        $final_year = $year - 2000;
        $final_day  = (int) $new_year->diff($dt)->days + 1;
    
        $zero_year      = str_pad((string) $final_year, 3, '0', STR_PAD_LEFT);
        $zero_final_day = str_pad((string) $final_day,  3, '0', STR_PAD_LEFT);
    
        return [
            'o_date' => ($hour . ":" . $min . ":" . $sec . " " . $final_day . " " . $zero_year),
        ];
    }
    
    $otc_current = get_otc_date('now');
    $o_date      = $otc_current['o_date'];
?>

<div class="tenon_header">
	<div class="tenon_header_td_left">
        <span class="title">Pith <?php
            if ($folderPath) {
                echo "- ";
                $is_mobile = function_exists('isMobile') ? isMobile() : false;
                $is_logged_in = ($login ?? false) === true;
                
                if ($is_mobile) {
                    $pith_title_trim = $is_logged_in ? 20 : 45;
                } else {
                    $pith_title_trim = $is_logged_in ? 35 : 45;
                }
                
                $safe_pith_title = htmlspecialchars($title ?? '', ENT_QUOTES, 'UTF-8');
                echo substr($safe_pith_title, 0, $pith_title_trim) . " ";
            }
        ?></span>
	</div>
	<div class="tenon_header_td_right">
	</div>
</div>
<div class="pith_table">
	<div class="pith_td">
		
    <div class='pith_sidebar'>
        <?php renderTree($rootPath, $rootPath, $filePath, $folderPath); ?>
    </div>

    <div class='main_content'>
        <div class='toolbar'>
            <div class='tool_path'>
                <span id="filename_display"><?php echo $filePath ? htmlspecialchars($filePath) : "No File Selected"; ?></span>
                <?php if ($filePath && ($safePath = getSafePath($rootPath . DIRECTORY_SEPARATOR . $filePath, $rootPath)) && is_file($safePath)): ?>
                    <span style="color: gray; margin-left: 4px;">[<?php 
                        $size = filesize($safePath);
                        echo ($size >= 1048576) ? number_format($size / 1048576, 1) . ' mb' : number_format($size / 1024, 1) . ' kb'; 
                    ?>]</span>
                <?php endif; ?>
            </div>

               <div class='file_data'>
                <div>
                    <?php if (isset($safePath) && $safePath && is_file($safePath)): ?>
                        <?php 
                            $file_mtime = filemtime($safePath);
                            $daysSince = floor((time() - $file_mtime) / 86400); 
                            $file_otc = get_otc_date($file_mtime);
                        ?>
                        <span class="file_meta">Modified: <?php echo $file_otc['o_date'] . " (" . date("d M 0y", $file_mtime) . ")"; ?><br><?php echo $daysSince; ?> Days Ago</span>
                    <?php endif; ?>
                </div>
                
                <?php if ($filePath): ?>
                    <div class='actions' style='flex-shrink: 0;'>
                        <button class='copy_btn' id='copyBtn' onclick='copyCode()'>Copy</button>
                    </div>
                <?php endif; ?>
            </div>
        </div>

        <div class='pith_viewer'>
            <div class='pith_viewer_inner'>
                <?php 
                    if ($filePath) {
                        $safePath = getSafePath($rootPath . DIRECTORY_SEPARATOR . $filePath, $rootPath);
                        if ($safePath && is_file($safePath)) {
                            $ext = strtolower(pathinfo($safePath, PATHINFO_EXTENSION));
                            $imageExts = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'svg', 'bmp', 'ico'];
                            if (in_array($ext, $imageExts)) {
                                echo "<div class='image_viewport'><img src='piths/source/" . htmlspecialchars($filePath) . "' alt=''></div>";
                            } else {
                                echo "<pre><code id='file_content'>" . htmlspecialchars(file_get_contents($safePath)) . "</code></pre>";
                            }
                        } else {
                            echo "<div class='empty_state'>Error: File not found or access denied.</div>";
                        }
                    }
                ?>
            </div>
        </div>
    </div>

	<a name="panels"></a>
	</div>
</div>

<script src='piths/pith/highlight.min.js'></script>
<script src='piths/pith/highlightjs-line-numbers.js'></script>
<script>
    hljs.highlightAll();
    hljs.initLineNumbersOnLoad();

    function copyCode() {
        const codeElement = document.getElementById('file_content');
        const btn = document.getElementById('copyBtn');
        
        if (!codeElement) return;
        const codeCells = codeElement.querySelectorAll('.hljs-ln-code');
        let text = "";
        
        if (codeCells.length > 0) {
            text = Array.from(codeCells).map(cell => cell.textContent).join('\n');
        } else {
            text = codeElement.textContent;
        }

        navigator.clipboard.writeText(text).then(() => {
            const originalText = btn.innerText;
            btn.innerText = "Copied";
            btn.classList.add('success');

            setTimeout(() => {
                btn.innerText = originalText;
                btn.classList.remove('success');
            }, 2000);
        }).catch(err => {
            console.error('Failed to copy: ', err);
            alert("Failed to copy text.");
        });
    }
</script>
<script>
    const detailsElements = document.querySelectorAll('details[data-path]');
    detailsElements.forEach(details => {
        const path = details.getAttribute('data-path');
        if (details.open) {
            sessionStorage.setItem('p-' + path, 'open');
        } 
        else if (sessionStorage.getItem('p-' + path) === 'open') {
            details.open = true;
        }
        details.addEventListener('toggle', (e) => {
        const path = details.getAttribute('data-path');
        if (details.open) {
            sessionStorage.setItem('p-' + path, 'open');
        } else {
            sessionStorage.removeItem('p-' + path);
        }
        });
        const summary = details.querySelector('summary');
        if (summary) {
            summary.addEventListener('click', (e) => {
                if (!details.open) {
                    e.preventDefault();
                    window.location.href = '?p=' + encodeURIComponent(path);
                }
            });
        }        
    });
</script>
Updates
Wedge - Android 126.026
Wedge - Linux 124.026
Shim - Android 117.026
Miter - 114.026
Kerf - Android 112.026
Dev
TVShow (227) 'CSA'
TVShow (228) 'APT'
TVProgram (83) 'BXT'
Miter Update(s)
Peen (Messaging)

Menu
Calendar
Project Tin (024/029)
Miter
RSS Feed
User Avatar
@vgmlr
=SUM(parts)