implemented preview of article markdown via AJAX

This commit is contained in:
2025-04-12 11:00:44 -06:00
parent 4f0262aa89
commit fb35e93226
8 changed files with 161 additions and 84 deletions
+25 -23
View File
@@ -2,56 +2,58 @@
<footer> <footer>
<?php <?php
function get_args_from_url() function get_args_from_url()
{ {
$args = []; $args = [];
$arr = explode('&', $_SERVER['QUERY_STRING']); $arr = explode('&', $_SERVER['QUERY_STRING']);
foreach ($arr as $a) { foreach ($arr as $a) {
$tmp = explode('=', $a); $tmp = explode('=', $a);
$key = $tmp[0]; $key = $tmp[0];
$value = $tmp[1]; $value = $tmp[1];
$args[$key] = $value; $args[$key] = $value;
} }
return $args; return $args;
} }
if ($_SERVER['SCRIPT_NAME'] == '/user.php') { if ($_SERVER['SCRIPT_NAME'] == '/user.php') {
$args = get_args_from_url(); $args = get_args_from_url();
if (isset($args['action'])) { if (isset($args['action'])) {
if ($args['action'] == 'signup') { if ($args['action'] == 'signup') {
echo ' echo '
<script src="js/profile_picture_cropper_modal.js"></script> <script src="js/profile_picture_cropper_modal.js"></script>
<script src="js/upload.js"></script> <script src="js/upload.js"></script>
'; ';
} else if ($args['action'] == 'view') { } else if ($args['action'] == 'view') {
echo ' echo '
<script src="js/user_actions_modal.js"></script> <script src="js/user_actions_modal.js"></script>
<script src="js/profile_picture_cropper_modal.js"></script> <script src="js/profile_picture_cropper_modal.js"></script>
<script src="js/upload.js"></script> <script src="js/upload.js"></script>
'; ';
} }
} }
} } else if ($_SERVER['SCRIPT_NAME'] == '/admin.php') {
if ($_SERVER['SCRIPT_NAME'] == '/admin.php') {
echo ' echo '
<script src="js/clickable_rows.js"></script> <script src="js/clickable_rows.js"></script>
'; ';
$args = get_args_from_url(); $args = get_args_from_url();
if (isset($args['view'])) { if (isset($args['view'])) {
if ($args['view'] == 'users') { if ($args['view'] == 'users') {
echo ' echo '
<script src="js/upload.js"></script> <script src="js/upload.js"></script>
'; ';
} }
} }
} } else if ($_SERVER['SCRIPT_NAME'] == '/compose.php') {
?> echo '
<script src="js/md_preview.js"></script>
';
}
?>
</footer> </footer>
</html> </html>
+60 -38
View File
@@ -4,77 +4,87 @@ require_once 'functions/functions.php';
include_once 'functions/article_functions.php'; include_once 'functions/article_functions.php';
if (!empty($_POST)) { if (!empty($_POST)) {
if (isset($_POST['article_id'])) { if (isset($_POST['article_id'])) {
$tags = []; $tags = [];
for ($i = 0; $i < count($_POST['tags']); $i++) for ($i = 0; $i < count($_POST['tags']); $i++)
$tags[] = $_POST['tags'][$i]; $tags[] = $_POST['tags'][$i];
update_article($_POST['article_id'], $_POST['title'], $_POST['excerpt'], $tags, $_POST['markdown']); update_article($_POST['article_id'], $_POST['title'], $_POST['excerpt'], $tags, $_POST['markdown']);
header('Location: articles.php?article_id=' . $_POST['article_id']); header('Location: articles.php?article_id=' . $_POST['article_id']);
} else { } else {
$id = create_article($_COOKIE['user_id'], $_POST['title'], $_POST['excerpt'], (isset($_POST['tags']) ? $_POST['tags'] : []), $_POST['markdown']); $id = create_article($_COOKIE['user_id'], $_POST['title'], $_POST['excerpt'], (isset($_POST['tags']) ? $_POST['tags'] : []), $_POST['markdown']);
header('Location: articles.php?article_id=' . $id); header('Location: articles.php?article_id=' . $id);
} }
} }
if (!isset($_COOKIE['role_id']) || $_COOKIE['role_id'] == 4) if (!isset($_COOKIE['role_id']) || $_COOKIE['role_id'] == 4)
header('Location: articles.php'); header('Location: articles.php');
include_once 'components/head.php'; include_once 'components/head.php';
if (isset($_GET['article_id'])) { if (isset($_GET['article_id'])) {
$article_id = $_GET['article_id']; $article_id = $_GET['article_id'];
$conn = get_connection(); $conn = get_connection();
$stmt = $conn->prepare('SELECT * FROM article WHERE article_id = ?'); $stmt = $conn->prepare('SELECT * FROM article WHERE article_id = ?');
$stmt->bind_param('i', $article_id); $stmt->bind_param('i', $article_id);
$stmt->execute(); $stmt->execute();
$article = $stmt->get_result()->fetch_assoc(); $article = $stmt->get_result()->fetch_assoc();
$stmt = $conn->prepare('SELECT tag_id FROM article_tags WHERE article_id = ?'); $stmt = $conn->prepare('SELECT tag_id FROM article_tags WHERE article_id = ?');
$stmt->bind_param('i', $article_id); $stmt->bind_param('i', $article_id);
$stmt->execute(); $stmt->execute();
$result = $stmt->get_result(); $result = $stmt->get_result();
$tags = []; $tags = [];
while ($row = $result->fetch_assoc()) while ($row = $result->fetch_assoc())
$tags[] = $row['tag_id']; $tags[] = $row['tag_id'];
$title = $article['title']; $title = $article['title'];
$excerpt = $article['excerpt']; $excerpt = $article['excerpt'];
$existing_markdown = read_file_one_string($_ENV['ARTICLES_FQ_PATH'] . $article_id . '/article.md'); $existing_markdown = read_file_one_string($_ENV['ARTICLES_FQ_PATH'] . $article_id . '/article.md');
} }
echo '
<div class="grid-2x1">
';
if (!empty($existing_markdown)) { if (!empty($existing_markdown)) {
echo form($article_id, $title, $excerpt, $existing_markdown, $tags); echo form($article_id, $title, $excerpt, $existing_markdown, $tags);
} else { } else {
echo form(); echo form();
} }
echo preview();
echo '
</div>
';
include_once 'components/foot.php'; include_once 'components/foot.php';
function form($article_id = null, $title = null, $excerpt = null, $existing_markdown = null, $tags = null) function form($article_id = null, $title = null, $excerpt = null, $existing_markdown = null, $tags = null)
{ {
$raw_tags = article_tags(); $raw_tags = article_tags();
$tags_boxes = ' $tags_boxes = '
<div class="checkboxes"> <div class="checkboxes">
<label>Tags</label> <label>Tags</label>
'; ';
foreach ($raw_tags as $row) { foreach ($raw_tags as $row) {
$tags_boxes .= ' $tags_boxes .= '
<label class="form_checkbox_container">' . $row['tag'] . ' <label class="form_checkbox_container">' . $row['tag'] . '
<input name="tags[]" value="' . $row['tag_id'] . '" type="checkbox"' . (isset($tags) && in_array($row['tag_id'], $tags) ? ' checked' : '') . '> <input name="tags[]" value="' . $row['tag_id'] . '" type="checkbox"' . (isset($tags) && in_array($row['tag_id'], $tags) ? ' checked' : '') . '>
<span class="checkmark"></span> <span class="checkmark"></span>
</label> </label>
'; ';
} }
$tags_boxes .= ' $tags_boxes .= '
</div> </div>
'; ';
$form = ' $form = '
<div class="form_card"> <div class="full-height">
<div class="form_card" id="composer">
<form method="post"> <form method="post">
' . (isset($article_id) ? '<input type="hidden" name="article_id" value="' . $article_id . '">' : '') . ' ' . (isset($article_id) ? '<input type="hidden" name="article_id" value="' . $article_id . '">' : '') . '
@@ -90,8 +100,20 @@ function form($article_id = null, $title = null, $excerpt = null, $existing_mark
<input class="button" type="submit" value="' . (isset($article_id) ? 'Update Article' : 'Create Article') . '"> <input class="button" type="submit" value="' . (isset($article_id) ? 'Update Article' : 'Create Article') . '">
</form> </form>
</div> </div>
</div>
'; ';
return $form;
}
return $form; function preview()
{
$preview = '
<div class="full-height">
<div class="form_card" id="md_preview">
</div>
</div>
';
return $preview;
} }
+58 -2
View File
@@ -168,6 +168,7 @@ code {
.view_card { .view_card {
margin: 0 auto; margin: 0 auto;
margin-top: 50px; margin-top: 50px;
margin-bottom: 50px;
width: 70%; width: 70%;
padding: 18px; padding: 18px;
height: max-content; height: max-content;
@@ -335,6 +336,7 @@ textarea {
margin: 15px 15px 15px 0px; margin: 15px 15px 15px 0px;
box-sizing: border-box; box-sizing: border-box;
transition-duration: 500ms; transition-duration: 500ms;
resize: none;
} }
input:focus, input:focus,
@@ -348,10 +350,12 @@ textarea:hover {
textarea { textarea {
border-radius: 18px; border-radius: 18px;
height: 500px; height: 70%;
} }
.checkboxes {} .checkboxes {
height: max-content;
}
input[type="radio"] { input[type="radio"] {
display: none; display: none;
@@ -639,6 +643,58 @@ input[type="file"] {
display: none; display: none;
} }
::selection {
color: black;
background: orange;
}
.grid-2x1 {
margin: 0 auto;
width: 90%;
display: grid;
grid-template-columns: 50% 50%;
}
.grid-2x1 .form_card,
.grid-2x1 #md_preview {
width: 80%;
height: 90%;
overflow-y: scroll;
box-sizing: border-box;
}
.grid-2x1 #composer {
padding-top: 150px;
}
#md_preview {
border: none;
display: block;
}
.full-height {
height: 700px;
}
::-webkit-scrollbar {
background-color: black;
border-radius: 18px;
}
::-webkit-scrollbar-button {
display: none;
}
::-webkit-scrollbar-track {
display: none;
}
::-webkit-scrollbar-thumb {
background-color: orange;
border-radius: 9px;
}
@media screen and (max-width: 992px) { @media screen and (max-width: 992px) {
h1 { h1 {
font-size: 10vw; font-size: 10vw;
-11
View File
@@ -92,12 +92,6 @@ function article_page_from_markdown($article_id)
$parsedown = new Parsedown(); $parsedown = new Parsedown();
$parsedown->setSafeMode(true); $parsedown->setSafeMode(true);
$article_content = $parsedown->text($markdown); $article_content = $parsedown->text($markdown);
$article_toc = '';
$lines = preg_split("/\r\n|\n|\r/", $article_content);
foreach ($lines as $line)
if (str_contains($line, '<h4>'))
$article_toc .= '<p>' . extract_content_from_tag($line, '<h4>', '</h4>') . '</p><br>';
$html = ' $html = '
<div class="article"> <div class="article">
@@ -116,14 +110,9 @@ function article_page_from_markdown($article_id)
</div> </div>
</div> </div>
<div class="line" style="margin-bottom: 15px;"></div> <div class="line" style="margin-bottom: 15px;"></div>
<div class="article_grid">
<div class="article_toc">
' . $article_toc . '
</div>
<div class="article_content"> <div class="article_content">
' . $article_content . ' ' . $article_content . '
</div> </div>
</div>
</div> </div>
'; ';
+1 -1
View File
@@ -1,7 +1,7 @@
<?php <?php
require_once 'vendor/autoload.php'; require_once 'vendor/autoload.php';
$dotenv = Dotenv\Dotenv::createImmutable(__DIR__ . '/../'); $dotenv = Dotenv\Dotenv::createImmutable(__DIR__ . '/..');
$dotenv->safeLoad(); $dotenv->safeLoad();
// Use environment variables for the database password and IP address. // Use environment variables for the database password and IP address.
+12
View File
@@ -0,0 +1,12 @@
<?php
session_start();
header("Content-Security-Policy: default-src 'self'; font-src 'self' https://www.nerdfonts.com/assets/fonts/Symbols-2048-em%20Nerd%20Font%20Complete.woff2; style-src 'self' 'unsafe-inline' https://nerdfonts.com/assets/css/webfont.css https://www.nerdfonts.com/assets/css/webfont.css; script-src 'self'; img-src 'self';");
require_once '../vendor/autoload.php';
$markdown = urldecode($_POST['markdown']);
$parsedown = new Parsedown();
$parsedown->setSafeMode(true);
$article_content = $parsedown->text($markdown);
echo $article_content;
+2 -5
View File
@@ -1,18 +1,15 @@
<?php <?php
// Initializes environment variables and other stuff.
// if (!isset($_SESSION['initialized']))
require_once 'functions/init.php'; require_once 'functions/init.php';
require_once 'functions/functions.php'; require_once 'functions/functions.php';
include_once 'components/head.php'; include_once 'components/head.php';
echo home_content(); echo home_content();
include_once 'components/foot.php'; include_once 'components/foot.php';
function home_content() function home_content()
{ {
$content = ' $content = '
<div class="article"> <div class="article">
<h1>Vintage Coding</h1> <h1>Vintage Coding</h1>
<h3>Intentional Craftsmanship Over Ephemeral Trends</h3> <h3>Intentional Craftsmanship Over Ephemeral Trends</h3>
@@ -34,5 +31,5 @@ function home_content()
</div> </div>
'; ';
return $content; return $content;
} }
+3 -4
View File
@@ -1,9 +1,8 @@
// Optional: Real-time preview using JavaScript
const textarea = document.querySelector('textarea'); const textarea = document.querySelector('textarea');
const preview = document.getElementById('preview'); const preview = document.getElementById('md_preview');
textarea.addEventListener('input', function() { textarea.addEventListener('input', function() {
fetch('preview.php', { // Create a separate preview.php file for real-time updates fetch('functions/preview.php', { // Create a separate preview.php file for real-time updates
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/x-www-form-urlencoded', 'Content-Type': 'application/x-www-form-urlencoded',
@@ -12,6 +11,6 @@ textarea.addEventListener('input', function() {
}) })
.then(response => response.text()) .then(response => response.text())
.then(data => { .then(data => {
preview.innerHTML = data; preview.innerHTML = decodeURIComponent(data);
}); });
}); });