- Reviews and forum posts/replies now use a modern comment layout: avatar + username/meta on the left, content/actions on the right.
Added sanitized Markdown rendering for reviews, forum topics, and replies. Added a local Markdown editor with toolbar and live visual preview for:Review body on [business.php](/Users/tyemeclifford/Documents/GH/MyKeyser/business.php) Forum topic body and reply body on [forum.php](/Users/tyemeclifford/Documents/GH/MyKeyser/forum.php) Added editor/comment styling in [assets/css/style.css](/Users/tyemeclifford/Documents/GH/MyKeyser/assets/css/style.css). Added the editor script at [assets/js/markdown-editor.js](/Users/tyemeclifford/Documents/GH/MyKeyser/assets/js/markdown-editor.js), included globally through [includes/footer.php](/Users/tyemeclifford/Documents/GH/MyKeyser/includes/footer.php).
This commit is contained in:
@@ -369,6 +369,100 @@ function userAvatarHtml(?array $user, string $class = 'avatar-sm'): string {
|
||||
$initial = strtoupper(substr($name, 0, 1) ?: 'U');
|
||||
return '<span class="avatar avatar-initial '.$class.'" aria-hidden="true">'.e($initial).'</span>';
|
||||
}
|
||||
function markdownSafeUrl(string $url): string {
|
||||
$url = trim(html_entity_decode($url, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'));
|
||||
$scheme = strtolower((string)(parse_url($url, PHP_URL_SCHEME) ?? ''));
|
||||
if ($scheme !== '' && !in_array($scheme, ['http','https','mailto'], true)) return '#';
|
||||
if ($url === '') return '#';
|
||||
return $url;
|
||||
}
|
||||
function markdownInline(string $text): string {
|
||||
$text = e($text);
|
||||
$text = preg_replace_callback('/`([^`]+)`/', fn($m) => '<code>'.e(html_entity_decode($m[1], ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8')).'</code>', $text);
|
||||
$text = preg_replace('/\*\*([^*]+)\*\*/', '<strong>$1</strong>', $text);
|
||||
$text = preg_replace('/\*([^*]+)\*/', '<em>$1</em>', $text);
|
||||
$text = preg_replace_callback('/\[([^\]]+)\]\(([^)]+)\)/', function($m) {
|
||||
return '<a href="'.e(markdownSafeUrl($m[2])).'" target="_blank" rel="noopener">'.$m[1].'</a>';
|
||||
}, $text);
|
||||
return $text;
|
||||
}
|
||||
function renderMarkdown(string $markdown): string {
|
||||
$markdown = str_replace(["\r\n", "\r"], "\n", trim($markdown));
|
||||
if ($markdown === '') return '';
|
||||
$lines = explode("\n", $markdown);
|
||||
$html = '';
|
||||
$para = [];
|
||||
$list = null;
|
||||
$quote = [];
|
||||
$code = false;
|
||||
$codeLines = [];
|
||||
|
||||
$flushPara = function() use (&$html, &$para) {
|
||||
if (!$para) return;
|
||||
$html .= '<p>'.str_replace("\n", '<br>', markdownInline(implode("\n", $para))).'</p>';
|
||||
$para = [];
|
||||
};
|
||||
$flushList = function() use (&$html, &$list) {
|
||||
if (!$list) return;
|
||||
$tag = $list['type'];
|
||||
$html .= '<'.$tag.'>';
|
||||
foreach ($list['items'] as $item) $html .= '<li>'.markdownInline($item).'</li>';
|
||||
$html .= '</'.$tag.'>';
|
||||
$list = null;
|
||||
};
|
||||
$flushQuote = function() use (&$html, &$quote) {
|
||||
if (!$quote) return;
|
||||
$html .= '<blockquote>'.str_replace("\n", '<br>', markdownInline(implode("\n", $quote))).'</blockquote>';
|
||||
$quote = [];
|
||||
};
|
||||
|
||||
foreach ($lines as $line) {
|
||||
if (preg_match('/^```/', $line)) {
|
||||
if ($code) {
|
||||
$html .= '<pre><code>'.e(implode("\n", $codeLines)).'</code></pre>';
|
||||
$code = false;
|
||||
$codeLines = [];
|
||||
} else {
|
||||
$flushPara(); $flushList(); $flushQuote();
|
||||
$code = true;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if ($code) { $codeLines[] = $line; continue; }
|
||||
if (trim($line) === '') {
|
||||
$flushPara(); $flushList(); $flushQuote();
|
||||
continue;
|
||||
}
|
||||
if (preg_match('/^(#{1,3})\s+(.+)$/', $line, $m)) {
|
||||
$flushPara(); $flushList(); $flushQuote();
|
||||
$level = strlen($m[1]) + 2;
|
||||
$html .= '<h'.$level.'>'.markdownInline($m[2]).'</h'.$level.'>';
|
||||
continue;
|
||||
}
|
||||
if (preg_match('/^>\s?(.*)$/', $line, $m)) {
|
||||
$flushPara(); $flushList();
|
||||
$quote[] = $m[1];
|
||||
continue;
|
||||
}
|
||||
if (preg_match('/^[-*]\s+(.+)$/', $line, $m)) {
|
||||
$flushPara(); $flushQuote();
|
||||
if (!$list || $list['type'] !== 'ul') $list = ['type'=>'ul','items'=>[]];
|
||||
$list['items'][] = $m[1];
|
||||
continue;
|
||||
}
|
||||
if (preg_match('/^\d+\.\s+(.+)$/', $line, $m)) {
|
||||
$flushPara(); $flushQuote();
|
||||
if (!$list || $list['type'] !== 'ol') $list = ['type'=>'ol','items'=>[]];
|
||||
$list['items'][] = $m[1];
|
||||
continue;
|
||||
}
|
||||
$flushList(); $flushQuote();
|
||||
$para[] = $line;
|
||||
}
|
||||
if ($code) $html .= '<pre><code>'.e(implode("\n", $codeLines)).'</code></pre>';
|
||||
$flushPara(); $flushList(); $flushQuote();
|
||||
return $html;
|
||||
}
|
||||
function uniqueForumSlug(string $title, int $id): string {
|
||||
$base = makeSlug($title) ?: 'topic';
|
||||
return $base.'-'.$id;
|
||||
|
||||
Reference in New Issue
Block a user