<?php
declare(strict_types=1);
require_once 'includes/config.php';
require_once 'includes/functions.php';
$cur_user = get_session_user();
$owner = strtolower(trim($_GET['owner'] ?? ''));
$list_id = preg_replace('/[^a-f0-9]/', '', $_GET['id'] ?? '');
if (!$owner || !$list_id) {
header('Location: ' . ($cur_user ? 'dashboard.php' : 'index.php'));
exit;
}
$list = get_list($owner, $list_id);
// Grant session access via share token if provided
if ($list && !empty($_GET['token']) && check_share_token($list, $_GET['token'])) {
grant_list_access($list['id']);
}
if (!$list || !can_view_list($list, $cur_user)) {
header('Location: ' . ($cur_user ? 'dashboard.php' : 'index.php'));
exit;
}
// Check password lock
session_start_safe();
if (!empty($list['password_hash'])) {
$access_key = 'list_access_' . $list_id;
if (empty($_SESSION[$access_key]) && !can_edit_list($list, $cur_user)) {
header('Location: ' . list_url($owner, $list_id));
exit;
}
}
$mode = $_GET['mode'] ?? 'print'; // 'print' or 'markdown'
/* ──────────────────────────────────────────────────────────────
Markdown generation (plain text, no HTML)
────────────────────────────────────────────────────────────── */
if ($mode === 'markdown') {
header('Content-Type: text/plain; charset=UTF-8');
header('Content-Disposition: inline; filename="' . preg_replace('/[^a-z0-9_-]/i', '_', $list['title']) . '.md"');
echo '# ' . $list['title'] . "\n";
echo "\n";
if ($list['description'] ?? '') {
echo '> ' . $list['description'] . "\n\n";
}
echo '**Owner:** @' . $list['owner'] . ' ' . "\n";
echo '**Updated:** ' . date('d F Y, H:i', strtotime($list['updated'])) . " \n";
echo '**Visibility:** ' . ($list['public'] ? 'Public' : 'Private') . "\n\n";
echo "---\n\n";
// Active tasks
echo '## Active Tasks (' . count($list['active']) . ")\n\n";
if (empty($list['active'])) {
echo "_No active tasks._\n\n";
} else {
foreach ($list['active'] as $i => $task) {
$pri = match($task['priority'] ?? 'normal') {
'high' => ' 🔴',
'low' => ' 🟢',
default => '',
};
echo '- [ ] ' . $task['text'] . $pri . "\n";
echo ' _Added ' . date('d M Y', strtotime($task['created'])) . '_' . "\n";
}
}
echo "\n---\n\n";
// Completed tasks
echo '## Completed (' . count($list['completed']) . ")\n\n";
if (empty($list['completed'])) {
echo "_No completed tasks._\n\n";
} else {
foreach ($list['completed'] as $task) {
echo '- [x] ' . $task['text'] . "\n";
echo ' _Completed ' . date('d M Y', strtotime($task['completed'] ?? '')) . '_' . "\n";
}
}
echo "\n---\n";
echo '_Generated by TaskGram on ' . date('d F Y, H:i') . "_\n";
exit;
}
/* ──────────────────────────────────────────────────────────────
HTML print view
────────────────────────────────────────────────────────────── */
$now = date('d F Y, H:i');
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?= h($list['title']) ?> — <?= APP_NAME ?></title>
<style>
/* ── Print base ──────────────────────────────────────────── */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: 'Arial', sans-serif;
font-size: 13px;
color: #000;
background: #fff;
padding: 16px 20px;
max-width: 860px;
margin: 0 auto;
}
/* ── Screen-only toolbar ─────────────────────────────────── */
.screen-toolbar {
background: #C0C0C0;
border-top: 2px solid #FFF;
border-left: 2px solid #FFF;
border-bottom:2px solid #404040;
border-right: 2px solid #404040;
padding: 6px 10px;
margin-bottom: 16px;
display: flex;
gap: 8px;
flex-wrap: wrap;
align-items: center;
}
.btn-print {
background: #C0C0C0;
border-top: 2px solid #FFF;
border-left: 2px solid #FFF;
border-bottom:2px solid #404040;
border-right: 2px solid #404040;
padding: 3px 14px;
cursor: pointer;
font-family: inherit;
font-size: 13px;
text-decoration: none;
display: inline-block;
color: #000;
}
.btn-print-primary {
background: #000080;
color: #FFF;
border-top: 2px solid #2040A0;
border-left: 2px solid #2040A0;
border-bottom:2px solid #000040;
border-right: 2px solid #000040;
}
/* ── Document header ─────────────────────────────────────── */
.doc-header {
border-bottom: 2px solid #000;
padding-bottom: 10px;
margin-bottom: 14px;
}
.doc-title {
font-size: 22px;
font-weight: bold;
margin-bottom: 4px;
}
.doc-meta {
font-size: 11px;
color: #555;
display: flex;
gap: 16px;
flex-wrap: wrap;
}
.doc-desc {
font-style: italic;
margin-top: 6px;
color: #333;
border-left: 3px solid #888;
padding-left: 8px;
}
/* ── Two-column layout ───────────────────────────────────── */
.two-col {
display: flex;
gap: 20px;
align-items: flex-start;
}
.col { flex: 1; min-width: 0; }
/* ── Section headings ────────────────────────────────────── */
.section-h {
font-size: 13px;
font-weight: bold;
background: #000080;
color: #FFF;
padding: 3px 8px;
margin-bottom: 6px;
display: flex;
justify-content: space-between;
}
/* ── Task rows ───────────────────────────────────────────── */
.task-row {
display: flex;
align-items: flex-start;
gap: 6px;
padding: 4px 4px 4px 6px;
border-bottom: 1px solid #DDD;
page-break-inside: avoid;
}
.task-row:nth-child(even) { background: #F4F4F4; }
.task-check {
flex-shrink: 0;
width: 14px;
height: 14px;
border: 1px solid #000;
margin-top: 1px;
display: flex;
align-items: center;
justify-content: center;
font-size: 10px;
}
.task-check.done { background: #E0E0E0; }
.task-body { flex: 1; min-width: 0; }
.task-txt { line-height: 1.4; word-break: break-word; }
.task-txt.completed-txt { text-decoration: line-through; color: #777; }
.task-date { font-size: 10px; color: #888; margin-top: 1px; }
.pri-dot {
display: inline-block;
width: 8px; height: 8px;
border-radius: 50%;
flex-shrink: 0;
margin-top: 4px;
}
.pri-high .pri-dot { background: #CC0000; }
.pri-normal .pri-dot { background: #000080; }
.pri-low .pri-dot { background: #006600; }
.empty-note { font-style: italic; color: #888; padding: 8px 4px; font-size: 12px; }
/* ── Markdown block (pre) ─────────────────────────────────── */
.md-source {
display: none;
background: #F8F8F8;
border: 1px solid #CCC;
padding: 12px;
font-family: monospace;
font-size: 12px;
white-space: pre;
overflow-x: auto;
margin-top: 20px;
}
.md-source.visible { display: block; }
/* ── Footer ─────────────────────────────────────────────── */
.doc-footer {
margin-top: 14px;
border-top: 1px solid #CCC;
padding-top: 6px;
font-size: 10px;
color: #888;
display: flex;
justify-content: space-between;
}
/* ── Print media ─────────────────────────────────────────── */
@media print {
.screen-toolbar { display: none !important; }
.md-source { display: none !important; }
body { padding: 0; font-size: 11px; }
.doc-title { font-size: 18px; }
.two-col { gap: 12px; }
@page { margin: 1.2cm; }
}
@media (max-width: 580px) {
.two-col { flex-direction: column; }
}
</style>
</head>
<body>
<!-- Screen-only toolbar -->
<div class="screen-toolbar">
<strong style="font-size:14px; color:#000080;">TaskGram</strong>
<span style="flex:1;"></span>
<a href="<?= h(list_url($owner, $list_id)) ?>" class="btn-print">← Back to List</a>
<a href="print.php?id=<?= h($list_id) ?>&owner=<?= h($owner) ?>&mode=markdown"
class="btn-print" target="_blank">💾 Download Markdown</a>
<a href="#md-block" class="btn-print" id="toggle-md-btn"
onclick="this.closest('.screen-toolbar').nextElementSibling && (function(){
var b=document.getElementById('md-block');
if(b){b.classList.toggle('visible');}
})(); return false;">View Markdown</a>
<button class="btn-print btn-print-primary" onclick="window.print()">🖶 Print</button>
</div>
<!-- Document -->
<div class="doc-header">
<div class="doc-title"><?= h($list['title']) ?></div>
<?php if ($list['description'] ?? ''): ?>
<div class="doc-desc"><?= h($list['description']) ?></div>
<?php endif; ?>
<div class="doc-meta" style="margin-top:6px;">
<span>Owner: <strong>@<?= h($list['owner']) ?></strong></span>
<span>Updated: <?= date('d F Y', strtotime($list['updated'])) ?></span>
<span>Active: <?= count($list['active']) ?></span>
<span>Completed: <?= count($list['completed']) ?></span>
<span>Visibility: <?= $list['public'] ? 'Public' : 'Private' ?></span>
</div>
</div>
<div class="two-col">
<!-- Active tasks column -->
<div class="col">
<div class="section-h">
<span>■ Active Tasks</span>
<span><?= count($list['active']) ?></span>
</div>
<?php if (empty($list['active'])): ?>
<div class="empty-note">No active tasks.</div>
<?php else: ?>
<?php foreach ($list['active'] as $task): ?>
<div class="task-row pri-<?= h($task['priority'] ?? 'normal') ?>">
<div class="pri-dot" title="<?= h(ucfirst($task['priority'] ?? 'normal')) ?> priority"></div>
<div class="task-check"></div>
<div class="task-body">
<div class="task-txt"><?= h($task['text']) ?></div>
<div class="task-date">Added <?= date('d M Y', strtotime($task['created'])) ?></div>
</div>
</div>
<?php endforeach; ?>
<?php endif; ?>
</div>
<!-- Completed column -->
<div class="col">
<div class="section-h">
<span>✓ Completed</span>
<span><?= count($list['completed']) ?></span>
</div>
<?php if (empty($list['completed'])): ?>
<div class="empty-note">No completed tasks.</div>
<?php else: ?>
<?php foreach ($list['completed'] as $task): ?>
<div class="task-row completed-item">
<div class="pri-dot" style="background:#888;"></div>
<div class="task-check done">✓</div>
<div class="task-body">
<div class="task-txt completed-txt"><?= h($task['text']) ?></div>
<div class="task-date">Completed <?= date('d M Y', strtotime($task['completed'] ?? '')) ?></div>
</div>
</div>
<?php endforeach; ?>
<?php endif; ?>
</div>
</div><!-- /two-col -->
<div class="doc-footer">
<span>Printed from <?= APP_NAME ?></span>
<span><?= h($now) ?></span>
</div>
<!-- Markdown source block (hidden by default on screen, never printed) -->
<pre class="md-source" id="md-block"><?php
$md = '# ' . $list['title'] . "\n\n";
if ($list['description'] ?? '') $md .= '> ' . $list['description'] . "\n\n";
$md .= '**Owner:** @' . $list['owner'] . ' ' . "\n";
$md .= '**Updated:** ' . date('d F Y, H:i', strtotime($list['updated'])) . " \n";
$md .= '**Visibility:** ' . ($list['public'] ? 'Public' : 'Private') . "\n\n";
$md .= "---\n\n## Active Tasks (" . count($list['active']) . ")\n\n";
if (empty($list['active'])) {
$md .= "_No active tasks._\n\n";
} else {
foreach ($list['active'] as $task) {
$pri = match($task['priority'] ?? 'normal') { 'high' => ' [HIGH]', 'low' => ' [LOW]', default => '' };
$md .= '- [ ] ' . $task['text'] . $pri . "\n";
$md .= ' _Added ' . date('d M Y', strtotime($task['created'])) . "_\n";
}
}
$md .= "\n---\n\n## Completed (" . count($list['completed']) . ")\n\n";
if (empty($list['completed'])) {
$md .= "_No completed tasks._\n\n";
} else {
foreach ($list['completed'] as $task) {
$md .= '- [x] ' . $task['text'] . "\n";
$md .= ' _Completed ' . date('d M Y', strtotime($task['completed'] ?? '')) . "_\n";
}
}
$md .= "\n---\n_Generated by TaskGram on " . $now . "_\n";
echo h($md);
?></pre>
</body>
</html>