Toby Chui 15 цаг өмнө
parent
commit
ec772c5bb1

+ 88 - 0
sd_card/www/admin/blog.html

@@ -0,0 +1,88 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <title>PostEngine Pro</title>
+    <meta name="viewport" content="width=device-width, initial-scale=1" >
+    <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
+    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.4.1/semantic.min.css" />
+    <script src="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.4.1/semantic.min.js"></script>
+    <style>
+        body{
+            background-color: rgb(243, 243, 243);
+            border-top: 1px solid #70aeff;
+        }
+
+        .ui.vertical.menu{
+            width: calc(15em - 3px) !important;
+        }
+
+        .postengine_context{
+            padding-left: calc(15em + 1px) !important;
+        }
+    </style>
+</head>
+<body>
+    <div class="ui container">
+
+    </div>
+    <div id="postengine_side_menu" class="ui left fixed inverted vertical menu">
+        <div class="item" style="pointer-events: none;">
+          <img class="ui fluid image" src="./img/post-engine.svg">
+        </div>
+        <div class="item">
+          <div class="header">Posts</div>
+          <div class="menu">
+            <a class="item active" xtab="postengine_all_post">All Posts</a>
+            <a class="item" xtab="postengine_new_post">Add New</a>
+            <a class="item" xtab="postengine_list_catergories">Categories</a>
+          </div>
+        </div>
+        <div class="item">
+          <div class="header">Media</div>
+          <div class="menu">
+            <a class="item" xtab="postengine_media_library">Library</a>
+            <a class="item" xtab="postengine_media_add_new">Add New</a>
+          </div>
+        </div>
+        <div class="item">
+          <div class="header">Pages</div>
+          <div class="menu">
+            <a class="item" xtab="postengine_pages_all">All Pages</a>
+            <a class="item" xtab="postengine_pages_add_new">Add New</a>
+          </div>
+        </div>
+        <div class="item">
+          <div class="header">Settings</div>
+          <div class="menu">
+            <a class="item" xtab="postengine_settings_general">General</a>
+            <a class="item" xtab="postengine_settings_permalinks">Permalinks</a>
+          </div>
+        </div>
+    </div>
+    <!-- Function Tabs -->
+    <div id="postengine_all_post" class="postengine_context"></div>
+    <div id="postengine_new_post" class="postengine_context" style="display:none;"></div>
+    <div id="postengine_list_catergories" class="postengine_context" style="display:none;"></div>
+    <div id="postengine_media_library" class="postengine_context" style="display:none;"></div>
+    <div id="postengine_media_add_new" class="postengine_context" style="display:none;"></div>
+    <div id="postengine_pages_all" class="postengine_context" style="display:none;"></div>
+    <div id="postengine_pages_add_new" class="postengine_context" style="display:none;"></div>
+    <div id="postengine_settings_general" class="postengine_context" style="display:none;"></div>
+    <div id="postengine_settings_permalinks" class="postengine_context" style="display:none;"></div>
+
+    <script>
+        // Add event listener to menu items with target attribute
+        $("#postengine_side_menu .item[xtab]").on("click", function() {
+            var targetId = $(this).attr("xtab");
+            $(".postengine_context").hide();
+            $("#postengine_side_menu .item.active").removeClass("active");
+            $(this).addClass("active");
+            $("#" + targetId).show();
+        });
+
+        //Load all the elements
+        $("#postengine_all_post").load("posteng/all.html");
+    </script>
+</body>
+</html>

+ 82 - 0
sd_card/www/admin/img/post-engine.svg

@@ -0,0 +1,82 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="圖層_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 width="256px" height="64px" viewBox="0 0 256 64" enable-background="new 0 0 256 64" xml:space="preserve">
+<path fill="#00A0E9" d="M5.334,15c0-5.523,4.477-10,10-10l0,0c5.523,0,10,4.477,10,10v34c0,5.522-4.477,10-10,10l0,0
+	c-5.523,0-10-4.478-10-10V15z"/>
+<path fill="#FFFFFF" d="M48.959,49.25c0,5.385-4.365,9.75-9.75,9.75l0,0c-5.385,0-9.75-4.365-9.75-9.75v-7.5
+	c0-5.385,4.365-9.75,9.75-9.75l0,0c5.385,0,9.75,4.365,9.75,9.75V49.25z"/>
+<g>
+	<path fill="#FFFFFF" d="M62.181,8.275h4.143l1.069,7.74c0.228,1.775,0.455,3.551,0.683,5.372h0.091
+		c0.319-1.821,0.66-3.619,1.002-5.372l1.73-7.74h3.438l1.752,7.74c0.341,1.73,0.638,3.551,0.979,5.372h0.114
+		c0.205-1.821,0.432-3.619,0.637-5.372l1.116-7.74h3.847l-2.937,16.959h-5.122l-1.525-7.376c-0.273-1.297-0.501-2.663-0.66-3.938
+		h-0.091c-0.228,1.274-0.432,2.641-0.683,3.938l-1.479,7.376h-5.031L62.181,8.275z"/>
+	<path fill="#FFFFFF" d="M84.037,18.746c0-4.188,3.028-6.783,6.146-6.783c3.733,0,5.554,2.709,5.554,6.237
+		c0,0.729-0.091,1.434-0.182,1.753h-7.604c0.365,1.775,1.594,2.504,3.233,2.504c0.933,0,1.798-0.273,2.731-0.819l1.343,2.436
+		c-1.343,0.956-3.118,1.48-4.621,1.48C86.905,25.553,84.037,23.048,84.037,18.746z M92.3,17.29c0-1.275-0.568-2.231-2.048-2.231
+		c-1.116,0-2.094,0.706-2.368,2.231H92.3z"/>
+	<path fill="#FFFFFF" d="M101.931,23.845h-0.114l-0.319,1.389h-3.164V7.046h4.052v4.371l-0.114,1.935
+		c0.956-0.865,2.163-1.388,3.346-1.388c3.096,0,5.076,2.595,5.076,6.556c0,4.484-2.663,7.034-5.44,7.034
+		C104.116,25.553,102.932,24.96,101.931,23.845z M106.529,18.609c0-2.276-0.661-3.346-2.095-3.346c-0.751,0-1.366,0.342-2.048,1.093
+		v5.121c0.614,0.569,1.297,0.751,1.912,0.751C105.528,22.229,106.529,21.205,106.529,18.609z"/>
+	<path fill="#FFFFFF" d="M112.563,23.117l2.322-2.8c1.184,1.024,2.754,1.73,4.098,1.73c1.502,0,2.208-0.569,2.208-1.48
+		c0-0.979-0.933-1.297-2.39-1.912l-2.163-0.91c-1.775-0.706-3.437-2.185-3.437-4.644c0-2.846,2.549-5.122,6.146-5.122
+		c1.958,0,4.029,0.751,5.509,2.231l-2.026,2.549c-1.115-0.842-2.163-1.297-3.483-1.297c-1.251,0-2.003,0.501-2.003,1.389
+		c0,0.956,1.047,1.32,2.549,1.912l2.117,0.842c2.095,0.842,3.347,2.253,3.347,4.644c0,2.822-2.368,5.304-6.465,5.304
+		C116.684,25.553,114.316,24.733,112.563,23.117z"/>
+	<path fill="#FFFFFF" d="M127.634,20.431v-4.985h-1.753v-3.004l2.003-0.16l0.455-3.437h3.347v3.437h3.05v3.164h-3.05v4.94
+		c0,1.434,0.683,2.003,1.639,2.003c0.41,0,0.865-0.137,1.184-0.25l0.638,2.937c-0.683,0.205-1.616,0.478-2.937,0.478
+		C128.955,25.553,127.634,23.481,127.634,20.431z"/>
+	<path fill="#FFFFFF" d="M137.015,8.389c0-1.251,0.888-2.071,2.254-2.071c1.343,0,2.253,0.82,2.253,2.071
+		c0,1.275-0.91,2.095-2.253,2.095C137.902,10.483,137.015,9.664,137.015,8.389z M137.242,12.281h4.052v12.953h-4.052V12.281z"/>
+	<path fill="#FFFFFF" d="M143.821,18.746c0-4.28,3.187-6.783,6.808-6.783c1.57,0,2.799,0.546,3.777,1.388l-1.889,2.572
+		c-0.593-0.478-1.093-0.683-1.639-0.683c-1.8,0-2.913,1.366-2.913,3.505c0,2.163,1.182,3.528,2.752,3.528
+		c0.821,0,1.594-0.387,2.254-0.911l1.572,2.664c-1.275,1.115-2.914,1.525-4.28,1.525C146.622,25.553,143.821,23.048,143.821,18.746z
+		"/>
+	<path fill="#FFFFFF" d="M156.776,7.046h3.983v10.106h0.112l3.824-4.871h4.439l-4.507,5.395l4.825,7.558h-4.393l-2.799-4.917
+		l-1.503,1.707v3.21h-3.983V7.046z"/>
+</g>
+<g>
+	<path fill="#FFFFFF" d="M63.797,35.285h5.963c3.711,0,6.784,1.342,6.784,5.531c0,4.03-3.096,5.804-6.692,5.804h-1.98v5.624h-4.074
+		V35.285z M69.647,43.412c1.98,0,2.937-0.935,2.937-2.596c0-1.706-1.093-2.299-3.051-2.299h-1.662v4.895H69.647z"/>
+	<path fill="#FFFFFF" d="M77.844,45.757c0-4.28,3.051-6.785,6.351-6.785c3.278,0,6.329,2.505,6.329,6.785
+		c0,4.302-3.05,6.806-6.329,6.806C80.895,52.563,77.844,50.059,77.844,45.757z M86.381,45.757c0-2.142-0.683-3.506-2.186-3.506
+		c-1.525,0-2.208,1.364-2.208,3.506c0,2.162,0.683,3.526,2.208,3.526C85.698,49.283,86.381,47.919,86.381,45.757z"/>
+	<path fill="#FFFFFF" d="M91.823,50.741l1.798-2.527c1.184,0.887,2.254,1.367,3.278,1.367c1.07,0,1.525-0.389,1.525-1.025
+		c0-0.774-1.252-1.138-2.573-1.662c-1.547-0.615-3.3-1.661-3.3-3.823c0-2.437,1.98-4.099,5.008-4.099
+		c2.026,0,3.528,0.819,4.621,1.662l-1.798,2.413c-0.934-0.66-1.844-1.094-2.686-1.094c-0.934,0-1.366,0.321-1.366,0.935
+		c0,0.774,1.116,1.069,2.458,1.549c1.617,0.613,3.415,1.547,3.415,3.893c0,2.389-1.866,4.233-5.395,4.233
+		C95.124,52.563,93.143,51.835,91.823,50.741z"/>
+	<path fill="#FFFFFF" d="M104.55,47.439v-4.985h-1.753v-3.005l2.003-0.159l0.455-3.436h3.346v3.436h3.051v3.164h-3.051v4.94
+		c0,1.435,0.684,2.004,1.64,2.004c0.409,0,0.865-0.136,1.184-0.251l0.637,2.938c-0.683,0.203-1.616,0.478-2.937,0.478
+		C105.871,52.563,104.55,50.491,104.55,47.439z"/>
+	<path fill="#FFFFFF" d="M114.476,35.285h10.744v3.392h-6.67v3.141h5.668v3.415h-5.668v3.597h6.92v3.415h-10.995V35.285z"/>
+	<path fill="#FFFFFF" d="M128.5,39.29h3.301l0.296,1.617h0.068c1.069-1.048,2.391-1.936,4.144-1.936
+		c2.822,0,4.028,2.027,4.028,5.259v8.014h-4.052v-7.513c0-1.797-0.478-2.298-1.503-2.298c-0.91,0-1.457,0.409-2.23,1.16v8.65H128.5
+		V39.29z"/>
+	<path fill="#FFFFFF" d="M142.705,54.428c0-1.069,0.615-1.934,1.821-2.549v-0.114c-0.683-0.454-1.184-1.138-1.184-2.23
+		c0-0.91,0.593-1.821,1.457-2.413V47.03c-0.934-0.637-1.774-1.843-1.774-3.368c0-3.141,2.617-4.69,5.416-4.69
+		c0.752,0,1.457,0.115,2.049,0.318h4.758v2.938h-1.98c0.183,0.365,0.342,0.957,0.342,1.57c0,3.028-2.3,4.349-5.168,4.349
+		c-0.432,0-0.956-0.068-1.524-0.228c-0.272,0.271-0.409,0.454-0.409,0.91c0,0.593,0.522,0.864,1.911,0.864h2.05
+		c3.186,0,5.007,0.98,5.007,3.323c0,2.733-2.845,4.666-7.353,4.666C145.142,57.683,142.705,56.752,142.705,54.428z M151.584,53.723
+		c0-0.774-0.684-0.934-1.891-0.934h-1.229c-0.934,0-1.435-0.044-1.844-0.159c-0.41,0.365-0.593,0.729-0.593,1.161
+		c0,0.91,1.094,1.367,2.732,1.367C150.423,55.158,151.584,54.542,151.584,53.723z M150.104,43.662c0-1.299-0.706-1.98-1.662-1.98
+		c-0.933,0-1.638,0.682-1.638,1.98c0,1.364,0.705,2.048,1.638,2.048C149.397,45.71,150.104,45.026,150.104,43.662z"/>
+	<path fill="#FFFFFF" d="M157.276,35.398c0-1.252,0.889-2.071,2.254-2.071c1.343,0,2.254,0.819,2.254,2.071
+		c0,1.275-0.911,2.095-2.254,2.095C158.165,37.493,157.276,36.674,157.276,35.398z M157.505,39.29h4.051v12.954h-4.051V39.29z"/>
+	<path fill="#FFFFFF" d="M164.813,39.29h3.3l0.296,1.617h0.069c1.069-1.048,2.39-1.936,4.143-1.936c2.822,0,4.028,2.027,4.028,5.259
+		v8.014h-4.051v-7.513c0-1.797-0.479-2.298-1.503-2.298c-0.91,0-1.456,0.409-2.231,1.16v8.65h-4.051V39.29z"/>
+	<path fill="#FFFFFF" d="M179.063,45.757c0-4.189,3.027-6.785,6.146-6.785c3.734,0,5.556,2.71,5.556,6.237
+		c0,0.728-0.092,1.435-0.183,1.753h-7.603c0.363,1.776,1.594,2.504,3.231,2.504c0.934,0,1.799-0.273,2.732-0.819l1.344,2.437
+		c-1.344,0.955-3.12,1.479-4.621,1.479C181.932,52.563,179.063,50.059,179.063,45.757z M187.327,44.299
+		c0-1.275-0.569-2.23-2.049-2.23c-1.115,0-2.095,0.704-2.367,2.23H187.327z"/>
+	<path fill="#FFFFFF" d="M198.894,35.285h5.964c3.711,0,6.784,1.342,6.784,5.531c0,4.03-3.097,5.804-6.693,5.804h-1.98v5.624h-4.074
+		V35.285z M204.743,43.412c1.98,0,2.937-0.935,2.937-2.596c0-1.706-1.093-2.299-3.05-2.299h-1.662v4.895H204.743z"/>
+	<path fill="#FFFFFF" d="M214.217,39.29h3.3l0.296,2.254h0.069c0.956-1.729,2.39-2.572,3.687-2.572c0.774,0,1.207,0.115,1.571,0.274
+		l-0.66,3.482c-0.478-0.115-0.865-0.204-1.434-0.204c-0.956,0-2.118,0.613-2.778,2.322v7.397h-4.051V39.29z"/>
+	<path fill="#FFFFFF" d="M223.778,45.757c0-4.28,3.051-6.785,6.352-6.785c3.277,0,6.328,2.505,6.328,6.785
+		c0,4.302-3.051,6.806-6.328,6.806C226.829,52.563,223.778,50.059,223.778,45.757z M232.314,45.757c0-2.142-0.684-3.506-2.185-3.506
+		c-1.526,0-2.208,1.364-2.208,3.506c0,2.162,0.682,3.526,2.208,3.526C231.631,49.283,232.314,47.919,232.314,45.757z"/>
+</g>
+</svg>

+ 17 - 1
sd_card/www/admin/index.html

@@ -4,7 +4,7 @@
     <meta charset="utf-8">
     <title>Admin Panel</title>
     <meta name="viewport" content="width=device-width, initial-scale=1" >
-
+    <link rel="icon" type="image/png" href="/favicon.png">
     <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
     <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.4.1/semantic.min.css" />
     <script src="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.4.1/semantic.min.js"></script>
@@ -34,6 +34,7 @@
             <img class="ui tiny image" src="img/icon.svg">
         </a>
         <a class="active yellow selectable icon item" onclick="switchFrame(event, this);" xframe="fs"><i class="folder icon"></i></a>
+        <a class="teal selectable icon item" onclick="switchFrame(event, this);" xframe="blog"><i class="ui edit icon"></i></a>
         <a class="violet selectable icon item" onclick="switchFrame(event, this);" xframe="search"><i class="ui search icon"></i></a>
         <a class="green selectable icon item" onclick="switchFrame(event, this);" xframe="shares"><i class="ui share alternate icon"></i></a>
         <a class="blue selectable icon item" onclick="switchFrame(event, this);" xframe="users"><i class="ui user icon"></i></a>
@@ -46,6 +47,9 @@
         <div id="fs" class="frameWrapper">
             <iframe src="fs.html"></iframe>
         </div>
+        <div id="blog" class="frameWrapper">
+            <iframe src="blog.html"></iframe>
+        </div>
         <div id="search" class="frameWrapper" style="display:none;">
             <iframe src="search.html"></iframe>
         </div>
@@ -76,6 +80,7 @@
             let targetFrameID = $(object).attr("xframe");
             $(".frameWrapper").hide();
             $("#" + targetFrameID).show();
+            window.location.hash = targetFrameID;
        }
 
        //Check login status
@@ -87,6 +92,17 @@
             });
        }
        initLoginCheck();
+
+       // Restore previously selected frame on page load
+       if (window.location.hash.length > 1) {
+            let targetFrameID = window.location.hash.substring(1);
+            if ($("#" + targetFrameID).length) {
+                $(".frameWrapper").hide();
+                $("#" + targetFrameID).show();
+                $(".mainmenu .item.active").removeClass("active");
+                $(".mainmenu .item[xframe='" + targetFrameID + "']").addClass("active");
+            }
+        }
     </script>
 </body>
 </html>

+ 58 - 0
sd_card/www/admin/posteng/all.html

@@ -0,0 +1,58 @@
+<br>
+<div class="ui container">
+    <h2 class="ui header">All Blog Posts</h2>
+    <table class="ui celled table">
+        <thead>
+            <tr>
+                <th>Post Name</th>
+                <th>Edit</th>
+                <th>Delete</th>
+            </tr>
+        </thead>
+        <tbody id="postTableBody">
+            <!-- Rows will be dynamically loaded here -->
+        </tbody>
+    </table>
+</div>
+
+<script>
+    $(document).ready(function() {
+        // Dummy function to fetch posts from an API
+        function fetchPosts() {
+            // Simulated API response
+            const posts = [
+                { id: 1, name: "First Blog Post" },
+                { id: 2, name: "Second Blog Post" },
+                { id: 3, name: "Third Blog Post" }
+            ];
+
+            // Clear the table body
+            $('#postTableBody').empty();
+
+            // Populate the table with posts
+            posts.forEach(post => {
+                $('#postTableBody').append(`
+                    <tr>
+                        <td>${post.name}</td>
+                        <td><button class="ui blue button edit-button" data-id="${post.id}">Edit</button></td>
+                        <td><button class="ui red button delete-button" data-id="${post.id}">Delete</button></td>
+                    </tr>
+                `);
+            });
+        }
+
+        // Call the fetchPosts function to load data
+        fetchPosts();
+
+        // Event listeners for Edit and Delete buttons
+        $(document).on('click', '.edit-button', function() {
+            const postId = $(this).data('id');
+            alert(`Edit post with ID: ${postId}`);
+        });
+
+        $(document).on('click', '.delete-button', function() {
+            const postId = $(this).data('id');
+            alert(`Delete post with ID: ${postId}`);
+        });
+    });
+</script>

BIN
sd_card/www/favicon.ico


BIN
sd_card/www/favicon.png


BIN
sd_card/www/favicon.psd


BIN
sd_card/www/img/logo.png


BIN
sd_card/www/img/logo.psd


+ 28 - 49
sd_card/www/index.html

@@ -2,6 +2,7 @@
 <html>
 <head>
 	<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
+	<link rel="icon" type="image/png" href="/favicon.png">
 	<!-- HTML Meta Tags -->
 	<title>Homepage | WebStick</title>
 	<meta name="description" content="A tiny web server powered by ESP8266, designed by tobychui">
@@ -29,6 +30,12 @@
 	<link rel="preconnect" href="https://fonts.googleapis.com">
 	<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
 	<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+TC:wght@400;500;700&display=swap" rel="stylesheet">
+
+	<style>
+		#banner{
+			background: linear-gradient(48deg, rgba(108,172,255,1) 8%, rgba(141,235,255,1) 65%)
+		}
+	</style>
 </head>
 <body>
 	<div class="ts-content">
@@ -36,24 +43,34 @@
 			<a class="item is-active" data-tab="home">Home</a>
 			<a class="item" data-tab="about">About</a>
 			<a class="item" data-tab="posts">Posts</a>
-			<a class="item" data-tab="downloads">Downloads</a>
 			<a class="item" data-tab="qr">QR</a>
 		</div>
 		<div class="has-top-spaced-small"></div>
-		<div class="ts-content" id="home">
-			<p>Welcome to the Home tab!</p>
+		<div class="ts-content " id="home">
+			<div id="banner" class="ts-content is-rounded is-padded has-top-spaced-large" style=" color: var(--ts-gray-50)">
+				<div style="max-width: 300px">
+					<div id="pageTitle" class="ts-header is-huge is-heavy">WebStick</div>
+					<p id="pageDesc">A personal web server hosted on an ESP8266 using a micro SD card</p>
+					<a href="/admin" class="ts-button is-outlined" style="color: var(--ts-gray-50)">Login</a>
+				</div>
+			</div>
+
+			<!-- Contact Information, change these to yours -->
+			<div class="ts-content is-center-aligned has-top-spaced-large">
+				<a href="https://www.youtube.com/channel/UCzbcGOZHO2BH-ANX7W0MGIg"><span class="ts-icon is-huge is-youtube-icon"></span></a>
+				<a class="has-start-spaced-large" href="https://blog.imuslab.com"><span class="ts-icon is-huge is-newspaper-icon"></span></a>
+				<a class="has-start-spaced-large" href="mailto:[email protected]"><span class="ts-icon is-huge is-envelope-icon"></span></a>
+				<a class="has-start-spaced-large" href="https://github.com/tobychui/webstick"><span class="ts-icon is-huge is-code-icon"></span></a>
+			</div>
 		</div>
 		<div class="ts-content" id="about">
-			
+			<!-- Dynmaically loaded by ajax from about.html -->
 		</div>
 		<div class="ts-content" id="posts">
-			<p>Check out the latest posts in the Posts tab.</p>
-		</div>
-		<div class="ts-content" id="downloads">
-			<p>Find useful resources in the Downloads tab.</p>
+			<!-- Dynmaically loaded by ajax from post.html -->
 		</div>
 		<div class="ts-content" id="qr">
-			<p>Generate QR codes in the QR tab.</p>
+			<!-- An iframe will be append here for the QR code generator -->
 		</div>
 		
 		<div class="ts-divider"></div>
@@ -63,48 +80,10 @@
 	</div>
 	
 	<script>
+		/* Ajax load of the pages */
 		$("#about").load("about.html");
+		$("#posts").load("posts.html");
 		$("#qr").html('<iframe src="tool/qr.html" style="border: none; width: 100%; height: calc(100vh - 200px);"></iframe>');
 	</script>
-	
-	<!-- 
-	<div class="ui container" align="center">
-	<br>
-		<div class="ui fluid card charactercard">
-			<div class="image" style="background-color: #ffe38d;" align="center">
-				<div class="character ring">
-					<div class="character infill">
-						<img src="img/selfie.jpg" style="border-radius: 50%; width: 100%; user-select: none; pointer-events: none;">
-					</div>    
-				</div>    
-				<h1 style="font-weight: bolder;">Hello visitor!</h1>
-				<div class="dark divider"></div>
-				<div class="ui breadcrumb">
-					<div class="section">Software Engineer</div>
-					<div class="divider"> / </div>
-					<div class="section">Electronics Maker</div>
-				</div>
-				<div class="linkbuttons">
-					<a class="ui fluid white button" href="about.html">About WebStick</a>
-					<a class="ui fluid white button" href="blog/index.html">My Blog</a>
-					<a class="ui fluid white button" href="down/"><i class="ui download icon"></i> Downloads</a>
-					<a class="ui fluid white button" href="tool/qr.html"><i class="ui qrcode icon"></i> QR Code Generator</a>
-					<div class="ui divider"></div>
-					<a class="ui fluid white button" href="admin/"><i class="ui sign in icon"></i> Admin Panel</a>
-				</div>
-		</div>
-		<div class="content" align="center">
-			<a class="blacklink" href="https://www.youtube.com/channel/UCzbcGOZHO2BH-ANX7W0MGIg"><i class="big youtube icon"></i></a>
-			<a class="blacklink" href="https://blog.imuslab.com"><i class="big wordpress icon"></i></a>
-			<a class="blacklink" href="mailto:[email protected]"><i class="big envelope icon"></i></a>
-			<a class="blacklink" href="https://github.com/tobychui/"><i class="big github icon"></i></a>
-			<a class="blacklink" href="https://www.facebook.com/ImusLaboratory"><i class="big facebook icon"></i></a>
-	  </div>
-	</div>
-		<br>
-		<p>This site is hosted on a WebStick designed by <a href="https://imuslab.com">imuslab</a></p>
-		<br>
-	</div>
--->
 </body>
 </html>

+ 19 - 2
sd_card/www/login.html

@@ -33,7 +33,7 @@
                           <span class="ts-icon is-lock-icon"></span>
                           <input type="password" name="password" placeholder="Password" required>
                       </div>
-                      <button class="ts-button is-fluid" onclick="login(event);">Login</button>
+                      <button id="loginBtn" class="ts-button is-fluid" onclick="login(event);">Login</button>
                   </div>
 
                   <a class="ts-text is-icon-link has-top-spaced-large" href="index.html">
@@ -46,6 +46,23 @@
     </div>
 
     <script>
+
+      $(document).ready(function() {
+        $('input[name="username"]').on('keypress', function(e) {
+          if (e.which === 13) { // Enter key
+            e.preventDefault();
+            $('input[name="password"]').focus();
+          }
+        });
+
+        $('input[name="password"]').on('keypress', function(e) {
+          if (e.which === 13) { // Enter key
+            e.preventDefault();
+            login(e);
+          }
+        });
+      });
+      
       function login(e){
         e.preventDefault(); // Prevent form submission
         // Get the input values
@@ -57,7 +74,7 @@
             method: "POST",
             success: function(data){
                 if (data.error != undefined){
-                  $('input[name="username"], input[name="password"]').addClass('is-negative');
+                  $('input[name="username"], input[name="password"]').parent().addClass('is-negative');
                 }else{
                     //Logged in
                     window.location.href = "/admin/";

+ 301 - 0
sd_card/www/posts.html

@@ -0,0 +1,301 @@
+<div class="ts-container is-very-narrow">
+    <p>Work in progress</p>
+</div>
+<script>
+     let loggedIn = false;
+
+    //Check the user has logged in
+    //Post editing function still require session check
+    //this just here to hide the edit buttons
+    $.get("/api/auth/chk", function(data){
+        if (data == false){
+            //User cannot use admin function. Hide the buttons.
+            $(".adminOnly").remove();
+            loggedIn = false;
+        }else{
+            loggedIn = true;
+        }
+        loadValue("blog-posts", function(){
+            initPosts();
+        });
+    });
+
+    //Initialize blog info
+    function initBlogInfo(){
+        loadValue("blog-title", function(title){
+            if (title.error != undefined || title == ""){
+                title = "WebStick";
+            }
+            document.title = decodeURIComponent(title);
+            $("#pageTitle").text(decodeURIComponent(title));
+        });
+
+        loadValue("blog-subtitle", function(title){
+            if (title.error != undefined || title == ""){
+                title = "A personal web server hosted on an ESP8266 using a micro SD card";
+            }
+            $("#pageDesc").text(decodeURIComponent(title));
+        });
+    }
+
+    $(document).ready(function(){
+        initBlogInfo();
+    });
+   
+
+
+    //Edit blog title and subtitles
+    function editBlogSubtitle(){
+        let newtitle = prompt("New Blog Subtitle", "");
+        if (newtitle != null) {
+            setValue("blog-subtitle", encodeURIComponent(newtitle), function(){
+                initBlogInfo();
+            })
+        } 
+    }
+
+    function editBlogTitle(){
+        let newtitle = prompt("New Blog Title", "");
+        if (newtitle != null) {
+            setValue("blog-title", encodeURIComponent(newtitle), function(){
+                initBlogInfo();
+            })
+        } 
+    }   
+
+    //Storage and loader utils
+    function setValue(key, value, callback){
+        $.get("/api/pref/set?key=" + key + "&value=" + value, function(data){
+            callback(data);
+        });
+    }
+
+    function loadValue(key, callback){
+        $.get("/api/pref/get?key=" + key, function(data){
+            callback(data);
+        });
+    }
+
+/*
+    New Post
+
+    New post is created via creating a markdown file in the server
+    side and open it with the markdown editor
+*/
+
+$("#createNewPostBtn").on("click", function(){
+    $("#newPostModal").toggle("fast");
+});
+
+function createNewPost(){
+    let filename = $("#newPostTitle").val().trim();
+    if (filename == ""){
+        alert("Post title cannot be empty.");
+        return;
+    }
+
+    if (filename.indexOf("/") >= 0){
+        //Contains /. Reject
+        alert("File name cannot contain path seperator");
+        return;
+    }
+
+    $("#confirmNewPostBtn").addClass("loading").addClass("disabled");
+    //Create the markdown file at the /blog/posts folder
+    const blob = new Blob(["# Hello World\n"], { type: 'text/plain' });
+    let storeFilename = parseInt(Date.now()/1000) + "_" + filename+'.md';
+    const file = new File([blob], storeFilename);
+    handleFile(file, "/blog/posts", function(){
+        //Update the post index
+        updatePostIndex();
+
+        $("#confirmNewPostBtn").removeClass("loading").removeClass("disabled");
+
+        //Open the markdown file in new tab
+        let hash = encodeURIComponent(JSON.stringify({
+            "filename": storeFilename,
+            "filepath": "/blog/posts/" + storeFilename
+        }))
+        window.open("/admin/mde/index.html#" + hash);
+
+        $("#newPostModal").hide();
+    });
+
+    
+}
+
+function handleFile(file, dir=currentPath, callback=undefined) {
+    // Perform actions with the selected file
+    var formdata = new FormData();
+    formdata.append("file1", file);
+    var ajax = new XMLHttpRequest();
+    ajax.addEventListener("load", function(event){
+        let responseText = event.target.responseText;
+        try{
+            responseText = JSON.parse(responseText);
+            if (responseText.error != undefined){
+                alert(responseText.error);
+            }
+        }catch(ex){
+
+        }
+        if (callback != undefined){
+            callback();
+        }
+    }, false); // doesnt appear to ever get called even upon success
+    ajax.addEventListener("error", errorHandler, false);
+    //ajax.addEventListener("abort", abortHandler, false);
+    ajax.open("POST", "/upload?dir=" + dir);
+    ajax.send(formdata);
+}
+
+function errorHandler(event) {
+    aelrt("New Post creation failed");
+    $("#pasteButton").removeClass("disabled");
+}
+
+/*
+    Post Edit functions
+*/
+
+function editPost(btn){
+    let postFilename = $(btn).attr("filename");
+    let hash = encodeURIComponent(JSON.stringify({
+        "filename": postFilename,
+        "filepath": "/blog/posts/" + postFilename
+    }))
+    window.open("/admin/mde/index.html#" + hash);
+}
+
+function deletePost(btn){
+    let postFilename = $(btn).attr("filename");
+    let postTitle = $(btn).attr("ptitle");
+    if (confirm("Confirm remove post titled: " + postTitle + "?")){
+        $.ajax({
+            url: "/api/fs/del?target=/blog/posts/" + postFilename,
+            method: "POST",
+            success: function(data){
+                if (data.error != undefined){
+                    alert("Post delete failed. See console for more info.");
+                    console.log(data.error);
+                }else{
+                   //Deleted
+                   initPosts();
+                }
+            }
+        });
+    }
+}
+
+/*
+    Rendering for Posts
+*/
+//Load a markdown file from URL and render it to target element
+function loadMarkdownToHTML(markdownURL, targetElement){
+    fetch(markdownURL).then( r => r.text() ).then( text =>{
+        var converter = new showdown.Converter();
+        let targetHTML = converter.makeHtml(text);
+        console.log(targetHTML);
+        $(targetElement).html(targetHTML);
+    });
+}
+
+function initPosts(){
+    $("#posttable").html("<div class='ui basic segment'><p><i class='ui loading spinner icon'></i> Loading Blog Posts</p></div>");
+    loadValue("blog-posts", function(data){
+        $("#posttable").html("");
+        try{
+            let postList = JSON.parse(decodeURIComponent(atob(data)));
+
+            //From latest to oldest
+            postList.reverse();
+            console.log("Post listed loaded: ", postList);
+            if (postList.length == 0){
+                $("#nopost").show();
+            }else{
+                $("#nopost").hide();
+                postList.forEach(postFilename => {
+                    renderPost(postFilename);
+                })
+            }
+        }catch(ex){
+            $("#nopost").show();
+        }
+        
+    })
+}
+
+function forceUpdatePostIndex(){
+    updatePostIndex(function(){
+        window.location.reload();
+    });
+}
+
+function updatePostIndex(callback=undefined){
+    let postList = [];
+    $.ajax({
+        url: "/api/fs/list?dir=/blog/posts",
+        success: function(data){
+            data.forEach(file => {
+                let filename = file.Filename;
+                let ext = filename.split(".").pop();
+                if (ext == "md" && file.IsDir == false){
+                    //Markdown file. Render it
+                    postList.push(filename);
+                }
+            });
+
+            setValue("blog-posts", btoa(encodeURIComponent(JSON.stringify(postList))), function(data){
+                console.log(data);
+                if (callback != undefined){
+                    callback();
+                }
+            });
+        }
+    });
+
+   
+}
+
+//Render post
+function renderPost(filename){
+    //Remove the timestamp
+    let postTitle = filename.split("_");
+    let timeStamp = postTitle.shift();
+    postTitle = postTitle.join("_");
+
+    //Pop the file extension
+    postTitle = postTitle.split(".");
+    postTitle.pop();
+    postTitle = postTitle.join(".");
+
+    var postTime = new Date(parseInt(timeStamp) * 1000).toLocaleDateString("en-US")
+    let postEditFeature = `<div class="adminOnly" style="position: absolute; top: 3em; right: 0.4em;">
+                <a class="ui basic mini icon button" onclick="editPost(this);" filename="${filename}" title="Edit Post"><i class="edit icon"></i></a>
+                <button class="ui basic mini icon button" onclick="deletePost(this);" ptitle="${postTitle}" filename="${filename}" title="Remove Post"><i class="red trash icon"></i></button>
+            </div>`;
+
+    if (!loggedIn){
+        postEditFeature = "";
+    }
+    //Create a wrapper element
+    $("#posttable").append(`
+        <div class="ui basic segment postObject" id="${timeStamp}">
+            <div class="ui divider"></div>
+            <h4 class="ui header">
+                <i class="blue paperclip icon"></i>
+                <div class="content">
+                   ${postTitle}
+                </div>
+            </h4>
+            ${postEditFeature}
+            <div class="postContent">
+
+            </div>
+            <small><i class="calendar alternate outline icon"></i> ${postTime}</small>
+        </div>
+    `);
+    let targetElement =  $("#" + timeStamp).find(".postContent");
+    loadMarkdownToHTML("/blog/posts/" + filename,targetElement);
+}
+</script>