@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 +42 -1
- package/Formatter.js +1 -1
- package/package.json +1 -1
- package/public/scripts.min.js +2 -2
- package/public/styles.min.css +70 -2
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
|
-
//
|
|
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
package/public/scripts.min.js
CHANGED
|
@@ -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");
|
|
2
|
-
/* source-hash:
|
|
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 */
|
package/public/styles.min.css
CHANGED
|
@@ -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:
|
|
2
|
-
/* source-hash:
|
|
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 */
|