posts.html 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301
  1. <div class="ts-container is-very-narrow">
  2. <p>Work in progress</p>
  3. </div>
  4. <script>
  5. let loggedIn = false;
  6. //Check the user has logged in
  7. //Post editing function still require session check
  8. //this just here to hide the edit buttons
  9. $.get("/api/auth/chk", function(data){
  10. if (data == false){
  11. //User cannot use admin function. Hide the buttons.
  12. $(".adminOnly").remove();
  13. loggedIn = false;
  14. }else{
  15. loggedIn = true;
  16. }
  17. loadValue("blog-posts", function(){
  18. initPosts();
  19. });
  20. });
  21. //Initialize blog info
  22. function initBlogInfo(){
  23. loadValue("blog-title", function(title){
  24. if (title.error != undefined || title == ""){
  25. title = "WebStick";
  26. }
  27. document.title = decodeURIComponent(title);
  28. $("#pageTitle").text(decodeURIComponent(title));
  29. });
  30. loadValue("blog-subtitle", function(title){
  31. if (title.error != undefined || title == ""){
  32. title = "A personal web server hosted on an ESP8266 using a micro SD card";
  33. }
  34. $("#pageDesc").text(decodeURIComponent(title));
  35. });
  36. }
  37. $(document).ready(function(){
  38. initBlogInfo();
  39. });
  40. //Edit blog title and subtitles
  41. function editBlogSubtitle(){
  42. let newtitle = prompt("New Blog Subtitle", "");
  43. if (newtitle != null) {
  44. setValue("blog-subtitle", encodeURIComponent(newtitle), function(){
  45. initBlogInfo();
  46. })
  47. }
  48. }
  49. function editBlogTitle(){
  50. let newtitle = prompt("New Blog Title", "");
  51. if (newtitle != null) {
  52. setValue("blog-title", encodeURIComponent(newtitle), function(){
  53. initBlogInfo();
  54. })
  55. }
  56. }
  57. //Storage and loader utils
  58. function setValue(key, value, callback){
  59. $.get("/api/pref/set?key=" + key + "&value=" + value, function(data){
  60. callback(data);
  61. });
  62. }
  63. function loadValue(key, callback){
  64. $.get("/api/pref/get?key=" + key, function(data){
  65. callback(data);
  66. });
  67. }
  68. /*
  69. New Post
  70. New post is created via creating a markdown file in the server
  71. side and open it with the markdown editor
  72. */
  73. $("#createNewPostBtn").on("click", function(){
  74. $("#newPostModal").toggle("fast");
  75. });
  76. function createNewPost(){
  77. let filename = $("#newPostTitle").val().trim();
  78. if (filename == ""){
  79. alert("Post title cannot be empty.");
  80. return;
  81. }
  82. if (filename.indexOf("/") >= 0){
  83. //Contains /. Reject
  84. alert("File name cannot contain path seperator");
  85. return;
  86. }
  87. $("#confirmNewPostBtn").addClass("loading").addClass("disabled");
  88. //Create the markdown file at the /blog/posts folder
  89. const blob = new Blob(["# Hello World\n"], { type: 'text/plain' });
  90. let storeFilename = parseInt(Date.now()/1000) + "_" + filename+'.md';
  91. const file = new File([blob], storeFilename);
  92. handleFile(file, "/blog/posts", function(){
  93. //Update the post index
  94. updatePostIndex();
  95. $("#confirmNewPostBtn").removeClass("loading").removeClass("disabled");
  96. //Open the markdown file in new tab
  97. let hash = encodeURIComponent(JSON.stringify({
  98. "filename": storeFilename,
  99. "filepath": "/blog/posts/" + storeFilename
  100. }))
  101. window.open("/admin/mde/index.html#" + hash);
  102. $("#newPostModal").hide();
  103. });
  104. }
  105. function handleFile(file, dir=currentPath, callback=undefined) {
  106. // Perform actions with the selected file
  107. var formdata = new FormData();
  108. formdata.append("file1", file);
  109. var ajax = new XMLHttpRequest();
  110. ajax.addEventListener("load", function(event){
  111. let responseText = event.target.responseText;
  112. try{
  113. responseText = JSON.parse(responseText);
  114. if (responseText.error != undefined){
  115. alert(responseText.error);
  116. }
  117. }catch(ex){
  118. }
  119. if (callback != undefined){
  120. callback();
  121. }
  122. }, false); // doesnt appear to ever get called even upon success
  123. ajax.addEventListener("error", errorHandler, false);
  124. //ajax.addEventListener("abort", abortHandler, false);
  125. ajax.open("POST", "/upload?dir=" + dir);
  126. ajax.send(formdata);
  127. }
  128. function errorHandler(event) {
  129. aelrt("New Post creation failed");
  130. $("#pasteButton").removeClass("disabled");
  131. }
  132. /*
  133. Post Edit functions
  134. */
  135. function editPost(btn){
  136. let postFilename = $(btn).attr("filename");
  137. let hash = encodeURIComponent(JSON.stringify({
  138. "filename": postFilename,
  139. "filepath": "/blog/posts/" + postFilename
  140. }))
  141. window.open("/admin/mde/index.html#" + hash);
  142. }
  143. function deletePost(btn){
  144. let postFilename = $(btn).attr("filename");
  145. let postTitle = $(btn).attr("ptitle");
  146. if (confirm("Confirm remove post titled: " + postTitle + "?")){
  147. $.ajax({
  148. url: "/api/fs/del?target=/blog/posts/" + postFilename,
  149. method: "POST",
  150. success: function(data){
  151. if (data.error != undefined){
  152. alert("Post delete failed. See console for more info.");
  153. console.log(data.error);
  154. }else{
  155. //Deleted
  156. initPosts();
  157. }
  158. }
  159. });
  160. }
  161. }
  162. /*
  163. Rendering for Posts
  164. */
  165. //Load a markdown file from URL and render it to target element
  166. function loadMarkdownToHTML(markdownURL, targetElement){
  167. fetch(markdownURL).then( r => r.text() ).then( text =>{
  168. var converter = new showdown.Converter();
  169. let targetHTML = converter.makeHtml(text);
  170. console.log(targetHTML);
  171. $(targetElement).html(targetHTML);
  172. });
  173. }
  174. function initPosts(){
  175. $("#posttable").html("<div class='ui basic segment'><p><i class='ui loading spinner icon'></i> Loading Blog Posts</p></div>");
  176. loadValue("blog-posts", function(data){
  177. $("#posttable").html("");
  178. try{
  179. let postList = JSON.parse(decodeURIComponent(atob(data)));
  180. //From latest to oldest
  181. postList.reverse();
  182. console.log("Post listed loaded: ", postList);
  183. if (postList.length == 0){
  184. $("#nopost").show();
  185. }else{
  186. $("#nopost").hide();
  187. postList.forEach(postFilename => {
  188. renderPost(postFilename);
  189. })
  190. }
  191. }catch(ex){
  192. $("#nopost").show();
  193. }
  194. })
  195. }
  196. function forceUpdatePostIndex(){
  197. updatePostIndex(function(){
  198. window.location.reload();
  199. });
  200. }
  201. function updatePostIndex(callback=undefined){
  202. let postList = [];
  203. $.ajax({
  204. url: "/api/fs/list?dir=/blog/posts",
  205. success: function(data){
  206. data.forEach(file => {
  207. let filename = file.Filename;
  208. let ext = filename.split(".").pop();
  209. if (ext == "md" && file.IsDir == false){
  210. //Markdown file. Render it
  211. postList.push(filename);
  212. }
  213. });
  214. setValue("blog-posts", btoa(encodeURIComponent(JSON.stringify(postList))), function(data){
  215. console.log(data);
  216. if (callback != undefined){
  217. callback();
  218. }
  219. });
  220. }
  221. });
  222. }
  223. //Render post
  224. function renderPost(filename){
  225. //Remove the timestamp
  226. let postTitle = filename.split("_");
  227. let timeStamp = postTitle.shift();
  228. postTitle = postTitle.join("_");
  229. //Pop the file extension
  230. postTitle = postTitle.split(".");
  231. postTitle.pop();
  232. postTitle = postTitle.join(".");
  233. var postTime = new Date(parseInt(timeStamp) * 1000).toLocaleDateString("en-US")
  234. let postEditFeature = `<div class="adminOnly" style="position: absolute; top: 3em; right: 0.4em;">
  235. <a class="ui basic mini icon button" onclick="editPost(this);" filename="${filename}" title="Edit Post"><i class="edit icon"></i></a>
  236. <button class="ui basic mini icon button" onclick="deletePost(this);" ptitle="${postTitle}" filename="${filename}" title="Remove Post"><i class="red trash icon"></i></button>
  237. </div>`;
  238. if (!loggedIn){
  239. postEditFeature = "";
  240. }
  241. //Create a wrapper element
  242. $("#posttable").append(`
  243. <div class="ui basic segment postObject" id="${timeStamp}">
  244. <div class="ui divider"></div>
  245. <h4 class="ui header">
  246. <i class="blue paperclip icon"></i>
  247. <div class="content">
  248. ${postTitle}
  249. </div>
  250. </h4>
  251. ${postEditFeature}
  252. <div class="postContent">
  253. </div>
  254. <small><i class="calendar alternate outline icon"></i> ${postTime}</small>
  255. </div>
  256. `);
  257. let targetElement = $("#" + timeStamp).find(".postContent");
  258. loadMarkdownToHTML("/blog/posts/" + filename,targetElement);
  259. }
  260. </script>