@lexho111/plainblog 0.4.2 → 0.5.0

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.
Files changed (47) hide show
  1. package/.vscode/settings.json +2 -0
  2. package/Blog.js +117 -195
  3. package/Formatter.js +21 -13
  4. package/README.md +2 -38
  5. package/blog.db +0 -0
  6. package/blog.json +87 -1
  7. package/build-styles.js +3 -3
  8. package/model/DatabaseModel2.js +86 -0
  9. package/model/fileModel.js +15 -5
  10. package/package.json +11 -9
  11. package/public/print.min.css +2 -0
  12. package/{scripts.min.js → public/scripts.min.js} +1 -1
  13. package/public/styles.min.css +68 -0
  14. package/src/print.scss +76 -0
  15. package/src/styles.css +18 -0
  16. package/test/blog.test.js +24 -7
  17. package/test/model.test.js +7 -5
  18. package/test/server.test.js +2 -1
  19. package/test/styles.test.js +42 -44
  20. package/blog.db-journal +0 -0
  21. package/model/DatabaseModel.js +0 -139
  22. package/streams.js +0 -17
  23. package/styles.min.css +0 -1
  24. package/test_1767117403635.db +0 -0
  25. package/test_1767117781437.db +0 -0
  26. package/test_1767117944836.db +0 -0
  27. package/test_1767119505118.db +0 -0
  28. package/test_1767173684248.db +0 -0
  29. package/test_1767173763155.db +0 -0
  30. package/test_1767173821563.db +0 -0
  31. package/test_1767260493607.db +0 -0
  32. package/test_1767281040439.db +0 -0
  33. package/test_1767281442334.db +0 -0
  34. package/test_1767286038587.db +0 -0
  35. package/test_1767286127364.db +0 -0
  36. package/test_1767286366239.db +0 -0
  37. package/test_1767286503638.db +0 -0
  38. package/test_1767286637739.db +0 -0
  39. package/test_1767292219862.db +0 -0
  40. package/test_1767292355190.db +0 -0
  41. package/test_server_db.db +0 -0
  42. package/test_styles_1.db +0 -0
  43. package/test_styles_2.db +0 -0
  44. package/test_styles_3.db +0 -0
  45. package/test_styles_4.db +0 -0
  46. /package/{styles.min.css.map → public/styles.min.css.map} +0 -0
  47. /package/src/{loader.js → fetchData.js} +0 -0
package/blog.json CHANGED
@@ -3,7 +3,93 @@
3
3
  "articles": [
4
4
  {
5
5
  "title": "hello",
6
- "content": "hello world!"
6
+ "content": "hML`JfDS]ARPVAfsGdogseNVYPXTpZaJhKJkPoMTWA\\q[CkNsXYrUGyGHWTDZpByubZMf_Y\\bMELh[]afM^XMAFqvxuyWat^hqSBMIifvbTdHNOQP_rbwwlWnGvVbPi]jlTIixyAaaU_]ecJYTjr]FqLq`UY\\XoYqhmuY`rvWH]EZO`tBHZSan`mAuhnwpXgdRJtjtH[",
7
+ "createdAt": "2026-01-07T12:49:43.098Z"
8
+ },
9
+ {
10
+ "title": "hello",
11
+ "content": "HgcFKFWDMl]cxiCVVrgFddJaOQCSPF^KYkROxkLTmxSc^ZssZh`ENyOXZn_DtOQe^V_vbu^KRonZ]bGcqsP]FYMmY\\YLjWknhiUksXwIUsvtpfXqViiKJ`xcmScZRXfAdHshvFlEHvPXWOfZRRIDIOxAafPoMD[ZgVlFkl\\Uw]RfubZWQ]`IHIUOKea_RTO`KiZisSA[",
12
+ "createdAt": "2026-01-07T12:49:43.444Z"
13
+ },
14
+ {
15
+ "title": "Test Title from Jest",
16
+ "content": "This is the content of the test article.",
17
+ "createdAt": "2026-01-07T13:10:50.239Z"
18
+ },
19
+ {
20
+ "title": "hello",
21
+ "content": "_\\jAuGsYByS]q\\QsnlqT\\]]TEDDalywtlmwmBZRlL[vtFdGqB\\[dlVx]A^[IV`KjhWU`DW`oorGwUlBU_IMCYoymIFi[`FpXrJfUmLZTRhoisxhXIcWqmk\\SPMV^]dvCuL_Pu^EAcMvBfadWFbUuSxFkjh\\KQyLnNWWsP`agQQkmU\\TAaOvYZAO\\FGtstSbaCJPXRQTO",
22
+ "createdAt": "2026-01-07T13:10:57.249Z"
23
+ },
24
+ {
25
+ "title": "hello",
26
+ "content": "HRkfwNRL_r\\DF]tyVBM[AMIjfMoBbohiqUf_T^]LwiJo[duXB`mQiRmmTkbMSJxvvHccfbXsdmKMbuYmxo[JGkwfCpqXSbhKiZID[vouGhbnLZ[wjkFFgm]fOaZcxoWduI`TNvin]\\dXoGW]YtueqSYPRLmGJswvUnyPHhfLUtf`]QNfM]hcvqqZasrQZVcXYjAnJxRP",
27
+ "createdAt": "2026-01-07T13:10:57.357Z"
28
+ },
29
+ {
30
+ "title": "hello",
31
+ "content": "UI\\KBodT[CgUg]L^WJ`Tuafq`rS]n\\T_eMqmBQqJVQisfABj^msS]u\\OEoJ]D^CtwE`UY[UbOICHcYPjNqNQ[LlwlJGPOoppSrkaEmDh_tm]CrrPGbBSPxgQQIZ^efcmITQQaAgT_vx[GUepg]LDagmTmihHbOIqHAa_NmRsX_EjxeE]EKhnHwFm]vFUpJTPFiDNU^BE",
32
+ "createdAt": "2026-01-07T13:17:47.559Z"
33
+ },
34
+ {
35
+ "title": "hello",
36
+ "content": "aMDnSSZDIZjbfrMlOcjMB\\CPup[GfoqHkLWAi]V_Ctkt[ekYRqIaufps[]TOMcw\\ojbVpPNv`lTTH\\[jbOcJXU[_tBSCsZW\\akiWuRf^mZGGrFxAq^oAdZjHwmJBjt^_GUXnHdXfjSrifiqXdAAdo]EM`aBFpJvyVLnLOfqrqoPrAIhRwoPHRy^lRCLyaS^VXChowHCn",
37
+ "createdAt": "2026-01-07T13:17:47.681Z"
38
+ },
39
+ {
40
+ "title": "Test Title from Jest",
41
+ "content": "This is the content of the test article.",
42
+ "createdAt": "2026-01-07T13:18:50.764Z"
43
+ },
44
+ {
45
+ "title": "hello",
46
+ "content": "[vL^GSDHJjBq^qdTMyyMm`SGhZhvgXsyTp[kYNkiBDavKevpIkSlWQr`bJaWyH]Qa`LQMKisdHeRvTKm`dviPLkLcJCtWbGPaDnNhJtEcEQM^MmP[RYERk\\uYeXruvPmJMjdvsOxA`btQtTmtBlOvrHLANJhcYRTAXmwOjLhp_SgkhWsB_pcIUQbd^X`RIWAdqUBPVmk",
47
+ "createdAt": "2026-01-07T13:18:52.387Z"
48
+ },
49
+ {
50
+ "title": "hello",
51
+ "content": "jRaJYEUFnMoEcjrWFREUuIQEoCtLoEuJSxJapEGpdPvq[GyrOqdSLY`VDlqSMTh\\BgybfwxfQQ_QqKkh\\YcEHYpA^W[ttGyGjA[TdZHn]]yoPWnFyFHJfAUkxkSM[RkQVqxAg_AGoJahaoOsNnoSGx^uM\\smWlWVDGvRcWVW\\rI_[lKnZEifvdPwkCcBRLXKGw[aNedN",
52
+ "createdAt": "2026-01-07T13:18:52.971Z"
53
+ },
54
+ {
55
+ "title": "Test Title from Jest",
56
+ "content": "This is the content of the test article.",
57
+ "createdAt": "2026-01-07T13:22:54.227Z"
58
+ },
59
+ {
60
+ "title": "Test Title from Jest",
61
+ "content": "This is the content of the test article.",
62
+ "createdAt": "2026-01-07T13:23:08.140Z"
63
+ },
64
+ {
65
+ "title": "hello",
66
+ "content": "M_RFGy[auVjFYypv^FdPqesIlcBkg]uoeIwMNLM]TOcDWKw[CFpsPpQWxSAXAupUdVsaiGJgLgIeuJEwBMXk`VG\\OoXIxCERYs`hqGRYGTnP]ZIrOSoJk[MFAxfVjJNMyZycryoRWr\\bhCXlurxgKPFSsTYbhlqZmrqaiJyvXpFsbiLQx^LBcZi`VqZSbjc`TNF_\\Eex",
67
+ "createdAt": "2026-01-07T13:26:02.437Z"
68
+ },
69
+ {
70
+ "title": "hello",
71
+ "content": "\\uRyVjar]vDZKHqRqJbEJpZbdKCWmQOGToNFQAqWdJQxSwmKTAGXywMDF`D\\nBA_eJxmFV^XtTuSGNJZTFjYvYibQIlgZdV[YXZpujmuWBgKNNBjrGZLVC[QlRZOwBdY^QPom\\b^WIIkeawsGBaafELGmfmTaIikRhCnCV_SYKEwogtqdfhkMkJslllumPyeGKqe\\cca",
72
+ "createdAt": "2026-01-07T13:26:02.816Z"
73
+ },
74
+ {
75
+ "title": "Test Title from Jest",
76
+ "content": "This is the content of the test article.",
77
+ "createdAt": "2026-01-07T13:26:07.866Z"
78
+ },
79
+ {
80
+ "title": "hello",
81
+ "content": "UecpUr`PFkpfKJ^V^q[T_lTr\\uP\\WFwcFtZgrdoao[japNpDycoAgWyELuIZtQroKxXSYdI]LrDTQSGqegWjgArWk[[eYXpy_OchWDywkus`GLcdwwr[qHoLINIdUoB^C]JcLfacOEJtMZOeWjRNbGcv\\XCfSAXUKIVqkPwJbaRTjlpGBxCGXxnchUhRoePYoLE]fexb",
82
+ "createdAt": "2026-01-07T13:45:46.304Z"
83
+ },
84
+ {
85
+ "title": "hello",
86
+ "content": "ef[SK^DuSJrKOlieeJHPLLoGgS[ZO\\gk_dWggGZchFewEnwspxJlhCWQ\\aTEKlDj\\a\\xRj\\uJANSkNLwrnPoOxJ[h]CrLppVtoeCEWPXRpCcoNFU`YA\\RPK\\dk\\Jp^fbdmC`Hpuf[ZYExseoPRbAYAaIolQ_mkJKRfrKsthmsBtCZdeQiBhY\\XAodKo_rOeQvtfNtDQB",
87
+ "createdAt": "2026-01-07T13:45:46.469Z"
88
+ },
89
+ {
90
+ "title": "Test Title from Jest",
91
+ "content": "This is the content of the test article.",
92
+ "createdAt": "2026-01-07T13:45:47.311Z"
7
93
  }
8
94
  ]
9
95
  }
package/build-styles.js CHANGED
@@ -3,7 +3,7 @@ import { pathToFileURL } from "url";
3
3
  import * as sass from "sass";
4
4
  import postcss from "postcss";
5
5
  import autoprefixer from "autoprefixer";
6
- import cssnano from "cssnano";
6
+ //import cssnano from "cssnano";
7
7
 
8
8
  // array of files or a single file
9
9
  export async function compileStyles(fileData) {
@@ -39,7 +39,7 @@ export async function compileStyles(fileData) {
39
39
 
40
40
  // 2. PostCSS (Autoprefixer + CSSNano)
41
41
  if (combinedCss) {
42
- const plugins = [autoprefixer(), cssnano()];
42
+ const plugins = [autoprefixer()];
43
43
  const result = await postcss(plugins).process(combinedCss, {
44
44
  from: undefined,
45
45
  });
@@ -57,7 +57,7 @@ export async function mergeStyles(...cssContents) {
57
57
  const combinedCss = cssContents.join("\n");
58
58
 
59
59
  if (combinedCss) {
60
- const plugins = [autoprefixer(), cssnano()];
60
+ const plugins = [autoprefixer()];
61
61
  const result = await postcss(plugins).process(combinedCss, {
62
62
  from: undefined,
63
63
  });
@@ -0,0 +1,86 @@
1
+ import { save as saveToFile, load as loadFromFile } from "./fileModel.js";
2
+
3
+ export default class DatabaseModel {
4
+ filename = "blog.json";
5
+
6
+ //new DatabaseModel(this.database);
7
+ constructor(options) {}
8
+
9
+ async initialize() {}
10
+
11
+ async getBlogTitle() {
12
+ let blogTitle = "";
13
+ try {
14
+ await loadFromFile(this.filename, (title) => {
15
+ blogTitle = title;
16
+ });
17
+ } catch (err) {
18
+ console.error(err);
19
+ }
20
+ return blogTitle;
21
+ }
22
+
23
+ //findAll();
24
+ //save(newArticleData);
25
+ async save(newArticle) {
26
+ if (!newArticle.createdAt) {
27
+ newArticle.createdAt = new Date().toISOString();
28
+ }
29
+
30
+ let blogTitle = "";
31
+ let articles = [];
32
+ try {
33
+ await loadFromFile(this.filename, (t, a) => {
34
+ blogTitle = t;
35
+ articles = a || [];
36
+ });
37
+ } catch (err) {
38
+ console.error(err);
39
+ }
40
+
41
+ articles.push(newArticle);
42
+ saveToFile(this.filename, { title: blogTitle, articles });
43
+ }
44
+
45
+ //updateBlogTitle(this.title);
46
+ async updateBlogTitle(newTitle) {
47
+ let articles = [];
48
+ try {
49
+ await loadFromFile(this.filename, (t, a) => {
50
+ //blogTitle = t;
51
+ articles = a || [];
52
+ });
53
+ } catch (err) {
54
+ console.error(err);
55
+ }
56
+ saveToFile(this.filename, { title: newTitle, articles });
57
+ }
58
+
59
+ //findAll(limit, 0, startID, endID);
60
+ async findAll(
61
+ limit = 4,
62
+ offset = 0,
63
+ startId = null,
64
+ endId = null,
65
+ order = "DESC"
66
+ ) {
67
+ let dbArticles = [];
68
+ try {
69
+ await loadFromFile(this.filename, (title, articles) => {
70
+ if (Array.isArray(articles)) {
71
+ // Sort by createdAt
72
+ articles.sort((a, b) => {
73
+ const dateA = new Date(a.createdAt || 0);
74
+ const dateB = new Date(b.createdAt || 0);
75
+ return order === "DESC" ? dateB - dateA : dateA - dateB;
76
+ });
77
+ // Apply pagination
78
+ dbArticles = articles.slice(offset, offset + limit);
79
+ }
80
+ });
81
+ } catch (err) {
82
+ console.error(err);
83
+ }
84
+ return dbArticles;
85
+ }
86
+ }
@@ -17,9 +17,19 @@ export async function save(filename, data) {
17
17
 
18
18
  /** load blog content from file */
19
19
  export async function load(filename, f) {
20
- const data = await fs.readFile(filename, "utf8");
21
- const jsonData = JSON.parse(data);
22
- const title = jsonData.title;
23
- const articles = jsonData.articles;
24
- f(title, articles);
20
+ try {
21
+ const data = await fs.readFile(filename, "utf8");
22
+ const jsonData = JSON.parse(data);
23
+ const title = jsonData.title;
24
+ const articles = jsonData.articles;
25
+ f(title, articles);
26
+ } catch (err) {
27
+ if (err.code === "ENOENT") {
28
+ const defaultData = { title: "Blog", articles: [] };
29
+ await save(filename, defaultData);
30
+ f(defaultData.title, defaultData.articles);
31
+ } else {
32
+ throw err;
33
+ }
34
+ }
25
35
  }
package/package.json CHANGED
@@ -1,10 +1,11 @@
1
1
  {
2
2
  "name": "@lexho111/plainblog",
3
- "version": "0.4.2",
3
+ "version": "0.5.0",
4
4
  "description": "A tool for creating and serving a minimalist, single-page blog.",
5
5
  "main": "index.js",
6
6
  "type": "module",
7
7
  "scripts": {
8
+ "dev": "node index.js",
8
9
  "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js",
9
10
  "lint": "eslint ."
10
11
  },
@@ -17,19 +18,20 @@
17
18
  "license": "ISC",
18
19
  "dependencies": {
19
20
  "autoprefixer": "^10.4.23",
20
- "cssnano": "^7.1.2",
21
- "node-fetch": "^3.3.2",
22
- "pg": "^8.16.3",
23
- "pg-hstore": "^2.3.4",
21
+ "child_process": "^1.0.2",
22
+ "crypto": "^1.0.1",
23
+ "fs": "^0.0.1-security",
24
+ "http": "^0.0.1-security",
25
+ "path": "^0.12.7",
24
26
  "postcss": "^8.4.35",
25
27
  "sass": "^1.97.1",
26
- "sequelize": "^6.37.7",
27
- "sqlite3": "^5.1.7"
28
+ "url": "^0.11.4",
29
+ "util": "^0.12.5"
28
30
  },
29
31
  "devDependencies": {
32
+ "dom-parser": "^1.1.5",
30
33
  "eslint": "^9.8.0",
31
34
  "eslint-plugin-jest": "^28.6.0",
32
- "jest": "^29.7.0",
33
- "dom-parser": "^1.1.5"
35
+ "jest": "^29.7.0"
34
36
  }
35
37
  }
@@ -0,0 +1,2 @@
1
+ *{font-size:.9rem}body{background-color:#fff;color:#000}h1{font:italic small-caps 700 2.5em/2.3em Verdana,sans-serif;letter-spacing:.1em;margin:0;padding:0}#wrapper{max-width:600px;width:100%}.grid{border:2px solid #d3d3d3;display:grid;gap:.25rem;grid-template-columns:1fr}.grid article h2{color:primary;margin-bottom:2px}.grid article{border:0 solid #ccc;border-radius:4px;min-width:0;overflow-wrap:break-word;padding:.25rem}.grid article .datetime{font-style:italic;margin:0}.grid article p{margin-bottom:0;margin-top:10px}.grid article a,.grid article a:link a:visited{color:#000}h1{color:primary}nav a{color:#3c3c3c;font-size:20px;text-decoration:underline}nav a[href*=login]{display:none}nav a:visited{color:#3c3c3c;text-decoration-color:#3c3c3c}
2
+ /*# sourceMappingURL=print-compiled.css.map */
@@ -1 +1 @@
1
- function _createForOfIteratorHelper(e,t){var r,n,o,a,i="undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(i)return o=!(n=!0),{s:function(){i=i.call(e)},n:function(){var e=i.next();return n=e.done,e},e:function(e){o=!0,r=e},f:function(){try{n||null==i.return||i.return()}finally{if(o)throw r}}};if(Array.isArray(e)||(i=_unsupportedIterableToArray(e))||t&&e&&"number"==typeof e.length)return i&&(e=i),a=0,{s:t=function(){},n:function(){return a>=e.length?{done:!0}:{done:!1,value:e[a++]}},e:function(e){throw e},f:t};throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function _unsupportedIterableToArray(e,t){var r;if(e)return"string"==typeof e?_arrayLikeToArray(e,t):"Map"===(r="Object"===(r={}.toString.call(e).slice(8,-1))&&e.constructor?e.constructor.name:r)||"Set"===r?Array.from(e):"Arguments"===r||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r)?_arrayLikeToArray(e,t):void 0}function _arrayLikeToArray(e,t){(null==t||t>e.length)&&(t=e.length);for(var r=0,n=Array(t);r<t;r++)n[r]=e[r];return n}function _regenerator(){var g,e="function"==typeof Symbol?Symbol:{},t=e.iterator||"@@iterator",r=e.toStringTag||"@@toStringTag";function n(e,t,r,n){var o,a,i,c,l,u,f,s,d,t=t&&t.prototype instanceof h?t:h,t=Object.create(t.prototype);return _regeneratorDefine2(t,"_invoke",(o=e,a=r,f=n||[],s=!1,d={p:u=0,n:0,v:g,a:p,f:p.bind(g,4),d:function(e,t){return i=e,c=0,l=g,d.n=t,y}},function(e,t,r){if(1<u)throw TypeError("Generator is already running");for(s&&1===t&&p(t,r),c=t,l=r;(m=c<2?g:l)||!s;){i||(c?c<3?(1<c&&(d.n=-1),p(c,l)):d.n=l:d.v=l);try{if(u=2,i){if(m=i[e=c?e:"next"]){if(!(m=m.call(i,l)))throw TypeError("iterator result is not an object");if(!m.done)return m;l=m.value,c<2&&(c=0)}else 1===c&&(m=i.return)&&m.call(i),c<2&&(l=TypeError("The iterator does not provide a '"+e+"' method"),c=1);i=g}else if((m=(s=d.n<0)?l:o.call(a,d))!==y)break}catch(e){i=g,c=1,l=e}finally{u=1}}return{value:m,done:s}}),!0),t;function p(e,t){for(c=e,l=t,m=0;!s&&u&&!r&&m<f.length;m++){var r,n=f[m],o=d.p,a=n[2];3<e?(r=a===t)&&(l=n[(c=n[4])?5:c=3],n[4]=n[5]=g):n[0]<=o&&((r=e<2&&o<n[1])?(c=0,d.v=t,d.n=n[1]):o<a&&(r=e<3||n[0]>t||a<t)&&(n[4]=e,n[5]=t,d.n=a,c=0))}if(r||1<e)return y;throw s=!0,t}}var y={};function h(){}function o(){}function a(){}var m=Object.getPrototypeOf,e=[][t]?m(m([][t]())):(_regeneratorDefine2(m={},t,function(){return this}),m),i=a.prototype=h.prototype=Object.create(e);function c(e){return Object.setPrototypeOf?Object.setPrototypeOf(e,a):(e.__proto__=a,_regeneratorDefine2(e,r,"GeneratorFunction")),e.prototype=Object.create(i),e}return _regeneratorDefine2(i,"constructor",o.prototype=a),_regeneratorDefine2(a,"constructor",o),_regeneratorDefine2(a,r,o.displayName="GeneratorFunction"),_regeneratorDefine2(i),_regeneratorDefine2(i,r,"Generator"),_regeneratorDefine2(i,t,function(){return this}),_regeneratorDefine2(i,"toString",function(){return"[object Generator]"}),(_regenerator=function(){return{w:n,m:c}})()}function _regeneratorDefine2(e,t,r,n){var a=Object.defineProperty;try{a({},"",{})}catch(e){a=0}(_regeneratorDefine2=function(e,t,r,n){function o(t,r){_regeneratorDefine2(e,t,function(e){return this._invoke(t,r,e)})}t?a?a(e,t,{value:r,enumerable:!n,configurable:!n,writable:!n}):e[t]=r:(o("next",0),o("throw",1),o("return",2))})(e,t,r,n)}function asyncGeneratorStep(e,t,r,n,o,a,i){try{var c=e[a](i),l=c.value}catch(e){return void r(e)}c.done?t(l):Promise.resolve(l).then(n,o)}function _asyncToGenerator(c){return function(){var e=this,i=arguments;return new Promise(function(t,r){var n=c.apply(e,i);function o(e){asyncGeneratorStep(n,t,r,o,a,"next",e)}function a(e){asyncGeneratorStep(n,t,r,o,a,"throw",e)}o(void 0)})}}var isLoading=!1;function handleScroll(){return _handleScroll.apply(this,arguments)}function _handleScroll(){return(_handleScroll=_asyncToGenerator(_regenerator().m(function e(){return _regenerator().w(function(e){for(;;)switch(e.n){case 0:if(isLoading)return e.a(2);e.n=1;break;case 1:if(document.documentElement.scrollHeight-window.innerHeight-window.scrollY<100)return e.n=2,loadMore();e.n=2;break;case 2:return e.a(2)}},e)}))).apply(this,arguments)}function loadMore(){return _loadMore.apply(this,arguments)}function _loadMore(){return(_loadMore=_asyncToGenerator(_regenerator().m(function e(){var t,r,n,o,a,i,c,l;return _regenerator().w(function(e){for(;;)switch(e.p=e.n){case 0:if(console.log("load more"),t=document.getElementById("articles"),t=t.querySelectorAll("article"),t=t[t.length-1],console.log("lastArticle",t),t){e.n=1;break}return e.a(2);case 1:if(r=parseInt(t.getAttribute("data-id")),console.log("lastId ".concat(r)),isNaN(r))return e.a(2);e.n=2;break;case 2:if((n=r-1)<1)return console.log("nextId < 1 ".concat(n<1)),window.removeEventListener("scroll",handleScroll),e.a(2);e.n=3;break;case 3:return isLoading=!0,o="/api/articles?startID=".concat(n,"&limit=5"),console.log("load more ".concat(o)),e.p=4,e.n=5,fetch(o);case 5:if((o=e.v).ok)return e.n=6,o.json();e.n=7;break;case 6:if((l=e.v).articles&&0<l.articles.length){a=_createForOfIteratorHelper(l.articles);try{for(a.s();!(i=a.n()).done;)fillWithContent((c=i.value).title,c.content,c.createdAt,c.id)}catch(e){a.e(e)}finally{a.f()}}case 7:e.n=9;break;case 8:e.p=8,l=e.v,console.error("Failed to load articles:",l);case 9:isLoading=!1;case 10:return e.a(2)}},e,null,[[4,8]])}))).apply(this,arguments)}function getFormattedDate(e){var t=e.getFullYear(),r=String(e.getMonth()+1).padStart(2,"0"),n=String(e.getDate()).padStart(2,"0"),o=String(e.getHours()).padStart(2,"0"),e=String(e.getMinutes()).padStart(2,"0");return"".concat(t,"/").concat(r,"/").concat(n," ").concat(o,":").concat(e)}function fillWithContent(e,t,r,n){var o=document.createElement("article"),n=(n&&o.setAttribute("data-id",n),document.createElement("h2")),a=document.createElement("span"),i=document.createElement("p");n.textContent=e,a.textContent=getFormattedDate(new Date(r)),i.textContent=t,o.appendChild(n),o.appendChild(a),o.appendChild(i),document.getElementById("articles").appendChild(o)}function test(){console.log("123")}document.addEventListener("DOMContentLoaded",function(){window.addEventListener("scroll",handleScroll)});
1
+ function _createForOfIteratorHelper(e,t){var r,n,o,a,i="undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(i)return o=!(n=!0),{s:function(){i=i.call(e)},n:function(){var e=i.next();return n=e.done,e},e:function(e){o=!0,r=e},f:function(){try{n||null==i.return||i.return()}finally{if(o)throw r}}};if(Array.isArray(e)||(i=_unsupportedIterableToArray(e))||t&&e&&"number"==typeof e.length)return i&&(e=i),a=0,{s:t=function(){},n:function(){return a>=e.length?{done:!0}:{done:!1,value:e[a++]}},e:function(e){throw e},f:t};throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function _unsupportedIterableToArray(e,t){var r;if(e)return"string"==typeof e?_arrayLikeToArray(e,t):"Map"===(r="Object"===(r={}.toString.call(e).slice(8,-1))&&e.constructor?e.constructor.name:r)||"Set"===r?Array.from(e):"Arguments"===r||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r)?_arrayLikeToArray(e,t):void 0}function _arrayLikeToArray(e,t){(null==t||t>e.length)&&(t=e.length);for(var r=0,n=Array(t);r<t;r++)n[r]=e[r];return n}function _regenerator(){var g,e="function"==typeof Symbol?Symbol:{},t=e.iterator||"@@iterator",r=e.toStringTag||"@@toStringTag";function n(e,t,r,n){var o,a,i,c,l,u,f,s,d,t=t&&t.prototype instanceof h?t:h,t=Object.create(t.prototype);return _regeneratorDefine2(t,"_invoke",(o=e,a=r,f=n||[],s=!1,d={p:u=0,n:0,v:g,a:p,f:p.bind(g,4),d:function(e,t){return i=e,c=0,l=g,d.n=t,y}},function(e,t,r){if(1<u)throw TypeError("Generator is already running");for(s&&1===t&&p(t,r),c=t,l=r;(m=c<2?g:l)||!s;){i||(c?c<3?(1<c&&(d.n=-1),p(c,l)):d.n=l:d.v=l);try{if(u=2,i){if(m=i[e=c?e:"next"]){if(!(m=m.call(i,l)))throw TypeError("iterator result is not an object");if(!m.done)return m;l=m.value,c<2&&(c=0)}else 1===c&&(m=i.return)&&m.call(i),c<2&&(l=TypeError("The iterator does not provide a '"+e+"' method"),c=1);i=g}else if((m=(s=d.n<0)?l:o.call(a,d))!==y)break}catch(e){i=g,c=1,l=e}finally{u=1}}return{value:m,done:s}}),!0),t;function p(e,t){for(c=e,l=t,m=0;!s&&u&&!r&&m<f.length;m++){var r,n=f[m],o=d.p,a=n[2];3<e?(r=a===t)&&(l=n[(c=n[4])?5:c=3],n[4]=n[5]=g):n[0]<=o&&((r=e<2&&o<n[1])?(c=0,d.v=t,d.n=n[1]):o<a&&(r=e<3||n[0]>t||a<t)&&(n[4]=e,n[5]=t,d.n=a,c=0))}if(r||1<e)return y;throw s=!0,t}}var y={};function h(){}function o(){}function a(){}var m=Object.getPrototypeOf,e=[][t]?m(m([][t]())):(_regeneratorDefine2(m={},t,function(){return this}),m),i=a.prototype=h.prototype=Object.create(e);function c(e){return Object.setPrototypeOf?Object.setPrototypeOf(e,a):(e.__proto__=a,_regeneratorDefine2(e,r,"GeneratorFunction")),e.prototype=Object.create(i),e}return _regeneratorDefine2(i,"constructor",o.prototype=a),_regeneratorDefine2(a,"constructor",o),_regeneratorDefine2(a,r,o.displayName="GeneratorFunction"),_regeneratorDefine2(i),_regeneratorDefine2(i,r,"Generator"),_regeneratorDefine2(i,t,function(){return this}),_regeneratorDefine2(i,"toString",function(){return"[object Generator]"}),(_regenerator=function(){return{w:n,m:c}})()}function _regeneratorDefine2(e,t,r,n){var a=Object.defineProperty;try{a({},"",{})}catch(e){a=0}(_regeneratorDefine2=function(e,t,r,n){function o(t,r){_regeneratorDefine2(e,t,function(e){return this._invoke(t,r,e)})}t?a?a(e,t,{value:r,enumerable:!n,configurable:!n,writable:!n}):e[t]=r:(o("next",0),o("throw",1),o("return",2))})(e,t,r,n)}function asyncGeneratorStep(e,t,r,n,o,a,i){try{var c=e[a](i),l=c.value}catch(e){return void r(e)}c.done?t(l):Promise.resolve(l).then(n,o)}function _asyncToGenerator(c){return function(){var e=this,i=arguments;return new Promise(function(t,r){var n=c.apply(e,i);function o(e){asyncGeneratorStep(n,t,r,o,a,"next",e)}function a(e){asyncGeneratorStep(n,t,r,o,a,"throw",e)}o(void 0)})}}var isLoading=!1;function handleScroll(){return _handleScroll.apply(this,arguments)}function _handleScroll(){return(_handleScroll=_asyncToGenerator(_regenerator().m(function e(){var t,r;return _regenerator().w(function(e){for(;;)switch(e.n){case 0:if(isLoading)return e.a(2);e.n=1;break;case 1:if(t=window.scrollY||document.documentElement.scrollTop||document.body.scrollTop,r=window.innerHeight||document.documentElement.clientHeight,(document.documentElement.scrollHeight||document.body.scrollHeight)-t-r<100)return e.n=2,loadMore();e.n=2;break;case 2:return e.a(2)}},e)}))).apply(this,arguments)}function loadMore(){return _loadMore.apply(this,arguments)}function _loadMore(){return(_loadMore=_asyncToGenerator(_regenerator().m(function e(){var t,r,n,o,a,i,c,l,u;return _regenerator().w(function(e){for(;;)switch(e.p=e.n){case 0:if(console.log("load more"),t=document.getElementById("articles"),t=t.querySelectorAll("article"),t=t[t.length-1],console.log("lastArticle",t),t){e.n=1;break}return e.a(2);case 1:if(r=parseInt(t.getAttribute("data-id")),console.log("lastId ".concat(r)),isNaN(r))return e.a(2);e.n=2;break;case 2:if((n=r-1)<1)return console.log("nextId < 1 ".concat(n<1)),window.removeEventListener("scroll",handleScroll),e.a(2);e.n=3;break;case 3:return o=!(isLoading=!0),a="/api/articles?startID=".concat(n,"&limit=5"),console.log("load more ".concat(a)),e.p=4,e.n=5,fetch(a);case 5:if((a=e.v).ok)return e.n=6,a.json();e.n=7;break;case 6:if((u=e.v).articles&&0<u.articles.length){i=_createForOfIteratorHelper(u.articles);try{for(i.s();!(c=i.n()).done;)fillWithContent((l=c.value).title,l.content,l.createdAt,l.id)}catch(e){i.e(e)}finally{i.f()}o=!0}case 7:e.n=9;break;case 8:e.p=8,u=e.v,console.error("Failed to load articles:",u);case 9:isLoading=!1,o&&handleScroll();case 10:return e.a(2)}},e,null,[[4,8]])}))).apply(this,arguments)}function getFormattedDate(e){var t=e.getFullYear(),r=String(e.getMonth()+1).padStart(2,"0"),n=String(e.getDate()).padStart(2,"0"),o=String(e.getHours()).padStart(2,"0"),e=String(e.getMinutes()).padStart(2,"0");return"".concat(t,"/").concat(r,"/").concat(n," ").concat(o,":").concat(e)}function fillWithContent(e,t,r,n){var o=document.createElement("article"),n=(n&&o.setAttribute("data-id",n),document.createElement("h2")),a=document.createElement("span"),i=document.createElement("p");n.textContent=e,a.textContent=getFormattedDate(new Date(r)),i.textContent=t,o.appendChild(n),o.appendChild(a),o.appendChild(i),document.getElementById("articles").appendChild(o)}function test(){console.log("123")}document.addEventListener("DOMContentLoaded",function(){window.addEventListener("scroll",handleScroll,{passive:!0}),handleScroll()});
@@ -0,0 +1,68 @@
1
+ body { font-family: Arial; } .grid {
2
+ border: 0 solid #000;
3
+ display: grid;
4
+ gap: 0.25rem;
5
+ grid-template-columns: 1fr;
6
+ }
7
+ .grid article {
8
+ border: 0 solid #ccc;
9
+ border-radius: 4px;
10
+ min-width: 0;
11
+ overflow-wrap: break-word;
12
+ padding: 0.25rem;
13
+ }
14
+ .grid article h2 {
15
+ color: rgb(53, 53, 53);
16
+ margin-bottom: 5px;
17
+ }
18
+
19
+ .grid article .datetime {
20
+ margin: 0;
21
+ color: rgb(117, 117, 117);
22
+ }
23
+
24
+ .grid article p {
25
+ margin-top: 10px;
26
+ margin-bottom: 0;
27
+ }
28
+
29
+ article a {
30
+ color: rgb(105, 105, 105);
31
+ }
32
+
33
+ article a:visited {
34
+ color: rgb(105, 105, 105);
35
+ }
36
+
37
+ h1 {
38
+ color: #696969;
39
+ }
40
+ nav a {
41
+ color: #3b40c1;
42
+ font-size: 20px;
43
+ text-decoration: underline;
44
+ }
45
+ nav a:visited {
46
+ color: #3b40c1;
47
+ text-decoration-color: #3b40c1;
48
+ }
49
+
50
+ #wrapper {
51
+ max-width: 500px;
52
+ width: 100%;
53
+ }
54
+
55
+ /* Mobile Layout (screens smaller than 1000px) */
56
+ @media screen and (max-width: 1000px) {
57
+ * {
58
+ font-size: 4vw;
59
+ }
60
+ #wrapper {
61
+ max-width: 100%;
62
+ width: 100%;
63
+ padding: 0 10px; /* Prevents text from touching the edges */
64
+ box-sizing: border-box;
65
+ }
66
+ }
67
+
68
+ /* source-hash: bcb6644ec5b5c6f9685c9ad6c14ee551a6f908b8a2c372d3294e2d2e80d17fb7 */
package/src/print.scss ADDED
@@ -0,0 +1,76 @@
1
+ $primary: black;
2
+ $secondary: black;
3
+ $nav: rgb(60, 60, 60);
4
+ $background: white;
5
+
6
+ * {
7
+ font-size: 0.5rem; /* Forces every single element to this size */
8
+ }
9
+
10
+
11
+ body {
12
+ color: $primary;
13
+ background-color: $background;
14
+ }
15
+
16
+ h1 { font: italic small-caps bold 2.5em/2.3em Verdana,sans-serif;
17
+ margin: 0; padding: 0;
18
+ letter-spacing: 0.1em;
19
+ }
20
+
21
+ #wrapper {
22
+ max-width: 600px; width: 100%;
23
+ }
24
+
25
+ .grid {
26
+ display: grid;
27
+ grid-template-columns: 1fr;
28
+ gap: 0.25rem;
29
+ border: 2px solid lightgray;
30
+
31
+ article {
32
+ h2 { color: primary;
33
+ margin-bottom: 2px;
34
+ }
35
+ padding: 0.25rem;
36
+ border: 0px solid #ccc;
37
+ border-radius: 4px;
38
+ min-width: 0; /* Allow grid items to shrink */
39
+ overflow-wrap: break-word; /* Break long words */
40
+ .datetime {
41
+ margin: 0;
42
+ font-style: italic;
43
+ }
44
+ p {
45
+ margin-top: 10px;
46
+ margin-bottom: 0;
47
+ }
48
+ a {
49
+ color: black;
50
+ }
51
+ a:link a:visited {
52
+ color: black;
53
+ }
54
+ }
55
+ }
56
+
57
+ h1 {
58
+ color: primary;
59
+ }
60
+
61
+ nav {
62
+ a {
63
+ text-decoration: underline;
64
+ color: $nav;
65
+ font-size: 20px;
66
+ }
67
+
68
+ a[href*="login"] {
69
+ display: none;
70
+ }
71
+
72
+ a:visited {
73
+ color: $nav;
74
+ text-decoration-color: $nav;
75
+ }
76
+ }
package/src/styles.css CHANGED
@@ -46,3 +46,21 @@ nav a:visited {
46
46
  color: #3b40c1;
47
47
  text-decoration-color: #3b40c1;
48
48
  }
49
+
50
+ #wrapper {
51
+ max-width: 500px;
52
+ width: 100%;
53
+ }
54
+
55
+ /* Mobile Layout (screens smaller than 1000px) */
56
+ @media screen and (max-width: 1000px) {
57
+ * {
58
+ font-size: 4vw;
59
+ }
60
+ #wrapper {
61
+ max-width: 100%;
62
+ width: 100%;
63
+ padding: 0 10px; /* Prevents text from touching the edges */
64
+ box-sizing: border-box;
65
+ }
66
+ }
package/test/blog.test.js CHANGED
@@ -1,3 +1,6 @@
1
+ import path from "path";
2
+ import { fileURLToPath } from "url";
3
+ import fs from "node:fs";
1
4
  import Blog from "../Blog.js";
2
5
  import Article from "../Article.js";
3
6
  import { jest } from "@jest/globals";
@@ -48,17 +51,31 @@ describe("test blog", () => {
48
51
  expect(html).toContain("<article");
49
52
  expect(myblog.articles.length).toBe(size);
50
53
  });
54
+ const __filename = fileURLToPath(import.meta.url);
55
+ const __dirname = path.dirname(__filename);
56
+ const publicDir = path.join(__dirname, "../public");
51
57
  test("set style", async () => {
52
- const myblog = new Blog();
53
58
  const styles = [
54
- "body { font-family: Courier; }",
55
- "body { background-color: black; color: white; }",
56
- "body { font-size: 1.2em; }",
59
+ {
60
+ style: "body { font-family: Courier; }",
61
+ expected: "font-family: Courier",
62
+ },
63
+ {
64
+ style: "body{ background-color:black; color:white; }",
65
+ expected: "background-color:black; color:white",
66
+ },
67
+ { style: "body{ font-size: 1.2em; }", expected: "font-size: 1.2em" },
57
68
  ];
58
69
  for (const style of styles) {
59
- myblog.setStyle(style);
60
- const html = await myblog.toHTML();
61
- expect(html).toContain(style);
70
+ const cssPath = path.join(publicDir, "styles.min.css");
71
+ if (fs.existsSync(cssPath)) {
72
+ await fs.promises.unlink(cssPath);
73
+ }
74
+ const myblog = new Blog();
75
+ myblog.setStyle(style.style);
76
+ await myblog.init();
77
+ const publicCSS = await fs.promises.readFile(cssPath, "utf8");
78
+ expect(publicCSS).toContain(style.expected);
62
79
  }
63
80
  });
64
81
  test("print method logs title and articles to console", async () => {
@@ -2,6 +2,7 @@ import Blog from "../Blog.js";
2
2
  import Article from "../Article.js";
3
3
  import { fetchData, postData } from "../model/APIModel.js";
4
4
  import { server } from "./simpleServer.js";
5
+ import { load as loadFromFile } from "../model/fileModel.js";
5
6
 
6
7
  function generateRandomContent(length) {
7
8
  let str = "";
@@ -26,18 +27,19 @@ describe("File Model test", () => {
26
27
  const article = new Article("hello", content);
27
28
  blog.addArticle(article);
28
29
 
29
- await blog.load("blog.json");
30
-
31
- expect(await blog.toHTML()).toContain(content);
32
- expect(blog).toBeDefined();
30
+ await loadFromFile("blog.json", async (title) => {
31
+ expect(await blog.toHTML()).toContain(content);
32
+ expect(blog).toBeDefined();
33
+ });
33
34
  });
34
35
  });
35
36
 
36
37
  describe("API Model test", () => {
37
- beforeAll(() => {
38
+ beforeAll((done) => {
38
39
  const port = 8081;
39
40
  server.listen(port, () => {
40
41
  console.log(`Simple server running at http://localhost:${port}/`);
42
+ done();
41
43
  });
42
44
  });
43
45
  afterAll(() => {
@@ -104,7 +104,8 @@ describe("server test", () => {
104
104
 
105
105
  expect(response.status).toBe(201);
106
106
  const responseData = await response.json();
107
- expect(responseData).toEqual(newArticle);
107
+ expect(responseData.title).toEqual(newArticle.title);
108
+ expect(responseData.content).toEqual(newArticle.content);
108
109
 
109
110
  expect(await blog.toHTML()).toContain(newArticle.content); // does blog contain my new article?
110
111
  });