@lexho111/plainblog 0.6.8 → 0.6.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/Blog.js CHANGED
@@ -465,6 +465,47 @@ export default class Blog {
465
465
  this.#handleLogout(req, res);
466
466
  return;
467
467
  }
468
+
469
+ if (req.method === "POST" && req.url === "/new") {
470
+ debug("/new");
471
+ if (!this.#isAuthenticated(req)) {
472
+ debug("not authenticated");
473
+ res.writeHead(403, { "Content-Type": "application/json" });
474
+ res.end(JSON.stringify({ error: "Forbidden" }));
475
+ return;
476
+ }
477
+ let body = "";
478
+
479
+ // 1. Collect data chunks
480
+ req.on("data", (chunk) => {
481
+ body += chunk.toString();
482
+ });
483
+
484
+ req.on("end", async () => {
485
+ try {
486
+ // 2. Parse x-www-form-urlencoded using URLSearchParams
487
+ const params = new URLSearchParams(body);
488
+
489
+ // 3. Convert to a plain object
490
+ const articleData = Object.fromEntries(params.entries());
491
+
492
+ console.log("New Article Data:", articleData);
493
+ // local
494
+ await this.#databaseModel.save(articleData); // --> to api server
495
+ this.postArticle(articleData);
496
+ // external
497
+
498
+ // Success response
499
+ res.writeHead(302, { Location: "/" });
500
+ res.end();
501
+ } catch (err) {
502
+ res.writeHead(400, { "Content-Type": "application/json" });
503
+ res.end(JSON.stringify({ error: "Failed to parse form data" }));
504
+ }
505
+ });
506
+ return;
507
+ }
508
+
468
509
  // load articles
469
510
  // GET artciles
470
511
  if (req.method === "GET" && req.url === "/") {
@@ -754,7 +795,7 @@ export default class Blog {
754
795
  // local
755
796
  await this.#databaseModel.save(newArticle); // --> to api server
756
797
  this.postArticle(newArticle);
757
- // extern
798
+ // external
758
799
  res.writeHead(201, { "Content-Type": "application/json" });
759
800
  res.end(JSON.stringify(newArticle));
760
801
  } else if (req.method === "DELETE") {
package/Formatter.js CHANGED
@@ -26,7 +26,7 @@ export function formatHTML(data) {
26
26
  const button = "";
27
27
  let form1 = "";
28
28
  if (data.loggedin) {
29
- form1 = `<form id="createNew" action="/" method="POST">
29
+ form1 = `<form id="createNew" action="/new" method="POST">
30
30
  <h3>Add a New Article</h3>
31
31
  <input type="text" id="title" class="form_element new_title wide" name="title" placeholder="Article Title" required>
32
32
  <textarea id="content" class="form_element new_content wide" name="content" placeholder="Article Content" required></textarea>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lexho111/plainblog",
3
- "version": "0.6.8",
3
+ "version": "0.6.9",
4
4
  "description": "A tool for creating and serving a minimalist, single-page blog.",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -1,2 +1,2 @@
1
- let isLoading=!1;function debounce(t,n=300){let a;return(...e)=>{clearTimeout(a),a=setTimeout(()=>{t.apply(this,e)},n)}}async function handleScroll(){isLoading||document.documentElement.scrollHeight-window.innerHeight-window.scrollY<100&&await loadMore()}async function loadMore(){console.log("load more");var e=document.getElementById("articles"),t=Array.from(e.querySelectorAll("article"));let n=NaN;for(let e=t.length-1;0<=e;e--){var a=t[e].getAttribute("data-date");if(a){a=parseInt(a,10);if(!isNaN(a)){n=a;break}}}if(console.log("lastTimestamp "+n),isNaN(n))window.removeEventListener("scroll",handleScroll);else{var e=n,l=(isLoading=!0,`/api/articles?enddate=${e}&limit=51`);console.log("load more "+l),console.log("endDate: "+new Date(e));try{var o=await fetch(l);if(o.ok){var i=(await o.json()).articles||[];if(0<i.length){let e=0;var r,d=document.createDocumentFragment();for(r of i)fillWithContent(r.title,r.content,r.createdAt,r.id,d)&&e++;document.getElementById("articles").appendChild(d),e<50&&window.removeEventListener("scroll",handleScroll)}else window.removeEventListener("scroll",handleScroll)}}catch(e){console.error("Failed to load articles:",e)}isLoading=!1}}function getFormattedDate(e){return e.getFullYear()+`/${String(e.getMonth()+1).padStart(2,"0")}/${String(e.getDate()).padStart(2,"0")} ${String(e.getHours()).padStart(2,"0")}:`+String(e.getMinutes()).padStart(2,"0")}async function handleArticleAction(e,t,n,a,l){var o;"delete"===e?confirm("Delete this article?")&&((o=await fetch("/api/articles?id="+t,{method:"DELETE"})).ok?l.remove():alert("Error: "+o.statusText)):"edit"===e&&(l=prompt("New Title",n),o=prompt("New Content",a),l)&&o&&((e=await fetch("/api/articles?id="+t,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({title:l,content:o})})).ok?window.location.reload():alert("Error: "+e.statusText))}function fillWithContent(a,l,e,o,t){if(o&&document.querySelector(`#articles > article[data-id='${o}']`))return console.log("article found. not adding it."),console.log(o+` ${a} `+e),!1;let i=document.createElement("article");var e=new Date(e),n=(null!=o&&i.setAttribute("data-id",o),i.setAttribute("data-date",e.getTime()),document.createElement("h2")),r=document.createElement("span"),d=document.createElement("p");if(null!==document.querySelector('a[href="/logout"]')){let n=document.createElement("div");var c=document.createElement("div"),s=document.createElement("div"),m=document.createElement("div");n.setAttribute("class","buttons"),c.setAttribute("class","btn edit"),s.setAttribute("class","btn delete"),m.setAttribute("class","btn light"),c.textContent="edit",s.textContent="delete",m.textContent="something else",n.appendChild(c),n.appendChild(s),n.appendChild(m),[{label:"edit",type:"edit",class:"btn edit"},{label:"delete",type:"delete",class:"btn delete"},{label:"something else",type:"other",class:"btn light"}].forEach(e=>{var t=document.createElement("div");t.className=e.class,t.textContent=e.label,t.addEventListener("click",()=>handleArticleAction(e.type,o,a,l,i)),n.appendChild(t)}),i.appendChild(n)}return n.textContent=a,r.textContent=getFormattedDate(e),d.textContent=l,i.appendChild(n),i.appendChild(r),i.appendChild(d),(t||document.getElementById("articles")).appendChild(i),!0}document.addEventListener("DOMContentLoaded",()=>{window.addEventListener("scroll",handleScroll);var e=document.getElementById("search");e&&e.addEventListener("input",debounce(async e=>{e=e.target.value;if(0<e.length){var e=await(await fetch("/api/articles?q="+encodeURIComponent(e))).json(),n=document.getElementById("articles");n.innerHTML="";let t=document.createDocumentFragment();(e.articles?e.articles.slice(0,50):[]).forEach(e=>fillWithContent(e.title,e.content,e.createdAt,e.id,t)),n.appendChild(t)}else window.location.reload()},300))});
2
- /* source-hash: 1fffde3e00f56a4fe51e53474262fcf2482e14e10c8acd05b62e87eee32ce5a3 */
1
+ let isLoading=!1;function debounce(t,n=300){let a;return(...e)=>{clearTimeout(a),a=setTimeout(()=>{t.apply(this,e)},n)}}async function handleScroll(){isLoading||document.documentElement.scrollHeight-window.innerHeight-window.scrollY<100&&await loadMore()}async function loadMore(){console.log("load more");var e=document.getElementById("articles"),t=Array.from(e.querySelectorAll("article"));let n=NaN;for(let e=t.length-1;0<=e;e--){var a=t[e].getAttribute("data-date");if(a){a=parseInt(a,10);if(!isNaN(a)){n=a;break}}}if(console.log("lastTimestamp "+n),isNaN(n))window.removeEventListener("scroll",handleScroll);else{var e=n,l=(isLoading=!0,`/api/articles?enddate=${e}&limit=51`);console.log("load more "+l),console.log("endDate: "+new Date(e));try{var o=await fetch(l);if(o.ok){var i=(await o.json()).articles||[];if(0<i.length){let e=0;var r,d=document.createDocumentFragment();for(r of i)fillWithContent(r.title,r.content,r.createdAt,r.id,d)&&e++;document.getElementById("articles").appendChild(d),e<50&&window.removeEventListener("scroll",handleScroll)}else window.removeEventListener("scroll",handleScroll)}}catch(e){console.error("Failed to load articles:",e)}isLoading=!1}}function getFormattedDate(e){return e.getFullYear()+`/${String(e.getMonth()+1).padStart(2,"0")}/${String(e.getDate()).padStart(2,"0")} ${String(e.getHours()).padStart(2,"0")}:`+String(e.getMinutes()).padStart(2,"0")}async function handleArticleAction(e,t,n,a,l){var o;"delete"===e?confirm("Delete this article?")&&((o=await fetch("/api/articles?id="+t,{method:"DELETE"})).ok?l.remove():alert("Error: "+o.statusText)):"edit"===e&&(l=prompt("New Title",n),o=prompt("New Content",a),l)&&o&&((e=await fetch("/api/articles?id="+t,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({title:l,content:o})})).ok?window.location.reload():alert("Error: "+e.statusText))}function fillWithContent(a,l,e,o,t){if(o&&document.querySelector(`#articles > article[data-id='${o}']`))return console.log("article found. not adding it."),console.log(o+` ${a} `+e),!1;let i=document.createElement("article");var e=new Date(e),n=(null!=o&&i.setAttribute("data-id",o),i.setAttribute("data-date",e.getTime()),document.createElement("h2")),r=document.createElement("span"),d=document.createElement("p");if(null!==document.querySelector('a[href="/logout"]')){let n=document.createElement("div");n.setAttribute("class","buttons"),[{label:"edit",type:"edit",class:"btn edit"},{label:"delete",type:"delete",class:"btn delete"},{label:"something else",type:"other",class:"btn light"}].forEach(e=>{var t=document.createElement("div");t.className=e.class,t.textContent=e.label,t.addEventListener("click",()=>handleArticleAction(e.type,o,a,l,i)),n.appendChild(t)}),i.appendChild(n)}return n.textContent=a,r.textContent=getFormattedDate(e),d.textContent=l,i.appendChild(n),i.appendChild(r),i.appendChild(d),(t||document.getElementById("articles")).appendChild(i),!0}document.addEventListener("DOMContentLoaded",()=>{window.addEventListener("scroll",handleScroll);var e=document.getElementById("search");e&&e.addEventListener("input",debounce(async e=>{e=e.target.value;if(0<e.length){var e=await(await fetch("/api/articles?q="+encodeURIComponent(e))).json(),n=document.getElementById("articles");n.innerHTML="";let t=document.createDocumentFragment();(e.articles?e.articles.slice(0,50):[]).forEach(e=>fillWithContent(e.title,e.content,e.createdAt,e.id,t)),n.appendChild(t)}else window.location.reload()},300))});
2
+ /* source-hash: eb3acbfa644abec843a08a736f6230b0032c4c03ed15970e5ac5741283f8924e */
@@ -1,2 +1,70 @@
1
- body{font-family:Arial,sans-serif}h1{color:#333}:root{--black:#111;--clearwhite:#fefefe;--white:#eee;--darkgray:rgba(54,54,54,.5);--text-primary:var(--black);--text-secondary:var(--clearwhite)}body,html{margin:0}html{font-size:clamp(1rem,1.5vw + .5rem,1.25rem)}body{background:var(--white_darker);color:var(--text-primary);font-family:Arial}nav{margin-top:10px}#header h1,nav{margin-left:25px}form{font-size:clamp(1rem,1.5vw + .5rem,1.25rem)}button,form input,form textarea{font-size:90%}button{width:-moz-fit-content;width:fit-content;field-sizing:content;padding:10px}.form_element{display:block;margin-bottom:10px;padding:4px}.wide{width:100%}.password{width:250px}.new_title{padding:10px}.new_content{height:500px;padding:10px;resize:none}#search{float:right;font-size:100%;margin:5px}.articles{border:0 solid #000;display:grid;gap:.25rem;grid-template-columns:1fr;max-width:800px}.articles article{border:2px solid #a9a9a9;border-radius:4px;margin-bottom:10px;min-width:0;overflow-wrap:break-word;padding:.4rem}.articles article h2{color:#353535;margin-bottom:5px}.articles article .datetime{color:#757575;margin:0}.articles article p{margin-bottom:0;margin-top:10px}article a,article a:visited,h1{color:#696969}h2{border:0 solid #000;margin-top:0}nav a{color:#3b40c1;font-size:20px;text-decoration:underline}nav a:visited{color:#3b40c1;text-decoration-color:#3b40c1}.loginform{margin-left:25px}#wrapper{margin-left:5px;max-width:1200px;padding:0 20px}#wrapper,.buttons{box-sizing:border-box;width:100%}.buttons{align-items:center;border:0 solid #000;display:flex;gap:5px;height:25px;list-style:none;margin:0 0 16px;padding:15px 15px 15px 0}.box{margin-left:25px}.btn{border:none;border:1px solid var(--text-primary);border-radius:0;box-shadow:3px 2px 2px var(--darkgray);color:var(--text-primary);cursor:pointer;font-size:1rem;font-weight:500;padding:.25rem .5rem;text-decoration:none;width:-moz-fit-content;width:fit-content}.btn:hover{background-color:#fff;border:2px solid var(--black);color:#000}.light{background:var(--clearwhite);color:var(--black)}.light:hover{background:var(--black);color:var(--clearwhite)}.hide-image{display:none}@media screen and (min-width:1000px){#wrapper{font-size:.75rem;margin:0 auto;max-width:1400px;padding:0 40px;width:90%}}.edit{background-color:blue}.delete,.edit{color:var(--clearwhite)}.delete{background-color:red}
2
- /* source-hash: 4fcd9901ab776e735cba281aaa4865ed96f897dd0aa4eaef774dee803966b720 */
1
+ body{font-family:Arial,sans-serif}h1{color:#333}:root{--black:#111;--clearwhite:#fefefe;--white:#eee;--darkgray:rgba(54,54,54,.5);--text-primary:var(--black);--text-secondary:var(--clearwhite)}body,html{margin:0}html{font-size:clamp(1rem,1.5vw + .5rem,1.25rem)}body{background:var(--white_darker);color:var(--text-primary);font-family:Arial}nav{margin-top:10px}#header h1,nav{margin-left:25px}form{font-size:clamp(1rem,1.5vw + .5rem,1.25rem)}button,form input,form textarea{font-size:90%}button{width:-moz-fit-content;width:fit-content;field-sizing:content;padding:10px}.form_element{display:block;margin-bottom:10px;padding:4px}.wide{width:100%}.password{width:250px}.new_title{padding:10px}.new_content{height:400px;padding:10px;resize:none}#createNew{max-width:93%;width:100%}#search{float:right;font-size:100%;margin:5px}.articles{border:0 solid #000;display:grid;gap:.25rem;grid-template-columns:1fr;max-width:800px}.articles article{border:2px solid #a9a9a9;border-radius:4px;margin-bottom:10px;min-width:0;overflow-wrap:break-word;padding:.4rem}.articles article h2{color:#353535;margin-bottom:5px}.articles article .datetime{color:#757575;margin:0}.articles article p{margin-bottom:0;margin-top:10px}article a,article a:visited,h1{color:#696969}h2{border:0 solid #000;margin-top:0}nav a{color:#3b40c1;font-size:20px;text-decoration:underline}nav a:visited{color:#3b40c1;text-decoration-color:#3b40c1}.loginform{margin-left:25px}#wrapper{margin-left:5px;max-width:1200px;padding:0 20px}#wrapper,.buttons{box-sizing:border-box;width:100%}.buttons{align-items:center;border:0 solid #000;display:flex;gap:5px;height:25px;list-style:none;margin:0 0 16px;padding:15px 15px 15px 0}.box{margin-left:25px}.btn{border:none;border:1px solid var(--text-primary);border-radius:0;box-shadow:3px 2px 2px var(--darkgray);color:var(--text-primary);cursor:pointer;font-size:1rem;font-weight:500;padding:.25rem .5rem;text-decoration:none;width:-moz-fit-content;width:fit-content}.btn:hover{background-color:#fff;border:2px solid var(--black);color:#000}.light{background:var(--clearwhite);color:var(--black)}.light:hover{background:var(--black);color:var(--clearwhite)}.hide-image{display:none}@media screen and (min-width:1000px){#wrapper{font-size:.75rem;margin:0 auto;max-width:1400px;padding:0 40px;width:90%}}.edit{background-color:blue}.delete,.edit{color:var(--clearwhite)}.delete{background-color:red}
2
+ /* source-hash: 43eccc89b2a2f88dfb627f25a53b70632b45b8e7a9dce7a627d082a071b2485f */margin: 0;
3
+ margin-bottom: 16px;
4
+ padding: 15px;
5
+ padding-left: 0px;
6
+ border: 0px solid black;
7
+ width: 100%; /* Now 100% will respect the parent's boundaries */
8
+ height: 25px;
9
+ }
10
+
11
+ .box {
12
+ margin-left: 25px;
13
+ }
14
+
15
+ .btn {
16
+ border: none;
17
+ color: var(--text-primary);
18
+ font-size: 1rem;
19
+ font-weight: 500;
20
+ text-decoration: none;
21
+ cursor: pointer;
22
+ padding: 0.25rem 0.5rem;
23
+ border-radius: 0px;
24
+ width: fit-content;
25
+ border: 1px solid var(--text-primary);
26
+ box-shadow: 3px 2px 2px var(--darkgray);
27
+ }
28
+
29
+ .btn:hover {
30
+ color: black;
31
+ background-color: white;
32
+ border: 2px solid var(--black);
33
+ }
34
+
35
+ .light {
36
+ color: var(--black);
37
+ background: var(--clearwhite);
38
+ }
39
+
40
+ .light:hover {
41
+ color: var(--clearwhite);
42
+ background: var(--black);
43
+ }
44
+
45
+ .hide-image {
46
+ display: none;
47
+ }
48
+
49
+ /* 2. DESKTOP ADJUSTMENTS (Screens larger than 1000px) */
50
+ @media screen and (min-width: 1000px) {
51
+ #wrapper {
52
+ width: 90%; /* Gives some breathing room on large monitors */
53
+ max-width: 1400px; /* Prevents it from getting absurdly wide */
54
+ padding: 0 40px;
55
+ margin: 0 auto;
56
+ /*zoom: 0.75; /* Reduces the entire layout to 80% of its current size */
57
+ font-size: 0.75rem; /* Instead of zoom, use a smaller base font size if you want things "smaller" */
58
+ }
59
+ }
60
+
61
+ .edit {
62
+ color: var(--clearwhite);
63
+ background-color: blue;
64
+ }
65
+ .delete {
66
+ color: var(--clearwhite);
67
+ background-color: red;
68
+ }
69
+
70
+ /* source-hash: b970c1968be8b30d22cd5de372f17d4ca5a348b98ba654a5932c82783a63cc76 */