diff --git a/package-lock.json b/package-lock.json index b12aab275..2ca681468 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,14 +8,18 @@ "name": "acmcsuf.com", "version": "1.1.0", "dependencies": { + "gfm.css": "^1.1.2", + "html-to-text": "^8.1.0", "rfs": "^9.0.6", - "rrule": "^2.6.8" + "rrule": "^2.6.8", + "rss": "^1.2.2" }, "devDependencies": { "@size-limit/file": "^7.0.4", "@sveltejs/adapter-static": "next", "@sveltejs/adapter-vercel": "next", "@sveltejs/kit": "next", + "@types/rss": "^0.0.29", "@typescript-eslint/eslint-plugin": "^4.31.1", "@typescript-eslint/parser": "^4.31.1", "axios": "^0.24.0", @@ -297,6 +301,18 @@ "npm": ">=7.0.0" } }, + "node_modules/@selderee/plugin-htmlparser2": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@selderee/plugin-htmlparser2/-/plugin-htmlparser2-0.6.0.tgz", + "integrity": "sha512-J3jpy002TyBjd4N/p6s+s90eX42H2eRhK3SbsZuvTDv977/E8p2U3zikdiehyJja66do7FlxLomZLPlvl2/xaA==", + "dependencies": { + "domhandler": "^4.2.0", + "selderee": "^0.6.0" + }, + "funding": { + "url": "https://ko-fi.com/killymxi" + } + }, "node_modules/@sindresorhus/is": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.2.0.tgz", @@ -405,6 +421,12 @@ "integrity": "sha512-LOnASQoeNZMkzexRuyqcBBDZ6rS+rQxUMkmj5A0PkhhiSZivLIuz6Hxyr1mkGoEZEkk66faROmpMi4fFkrKsBA==", "dev": true }, + "node_modules/@types/rss": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/rss/-/rss-0.0.29.tgz", + "integrity": "sha512-9hAYKyOcoxWqkJMue6bi0fcNXxjJiRutVvqyImy732LmTCxRz9BYPmSmujG7uddK0ZEP/35K3QVZ0sfAVyPnQw==", + "dev": true + }, "node_modules/@types/sass": { "version": "1.16.1", "resolved": "https://registry.npmjs.org/@types/sass/-/sass-1.16.1.tgz", @@ -851,6 +873,11 @@ "node": ">= 0.8" } }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -903,6 +930,14 @@ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true }, + "node_modules/deepmerge": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", + "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -933,6 +968,11 @@ "node": ">=8" } }, + "node_modules/discontinuous-range": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/discontinuous-range/-/discontinuous-range-1.0.0.tgz", + "integrity": "sha1-44Mx8IRLukm5qctxx3FYWqsbxlo=" + }, "node_modules/discord-api-types": { "version": "0.23.1", "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.23.1.tgz", @@ -986,6 +1026,57 @@ "node": ">=6.0.0" } }, + "node_modules/dom-serializer": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.3.2.tgz", + "integrity": "sha512-5c54Bk5Dw4qAxNOI1pFEizPSjVsx5+bpJKmL2kPn8JhBUq2q09tTCa3mjijun2NfK78NMouDYNMBkOrPZiS+ig==", + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.2.0", + "entities": "^2.0.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.2.0.tgz", + "integrity": "sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ] + }, + "node_modules/domhandler": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.0.tgz", + "integrity": "sha512-fC0aXNQXqKSFTr2wDNZDhsEYjCiYsDWl3D01kwt25hm1YIPyDGHvvi3rw+PLqHAl/m71MaiF7d5zvBr0p5UB2g==", + "dependencies": { + "domelementtype": "^2.2.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", + "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", + "dependencies": { + "dom-serializer": "^1.0.1", + "domelementtype": "^2.2.0", + "domhandler": "^4.2.0" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, "node_modules/dot-prop": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-6.0.1.tgz", @@ -1028,6 +1119,14 @@ "node": ">=8.6" } }, + "node_modules/entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/es6-promise": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.3.1.tgz", @@ -1726,6 +1825,12 @@ "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", "dev": true }, + "node_modules/gfm.css": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/gfm.css/-/gfm.css-1.1.2.tgz", + "integrity": "sha512-KhK3rqxMj+UTLRxWnfUA5n8XZYMWfHrrcCxtWResYR2B3hWIqBM6v9FPGZSlVuX+ScLewizOvNkjYXuPs95ThQ==", + "deprecated": "Package no longer supported. Please use github-markdown-css instead." + }, "node_modules/glob": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", @@ -1820,6 +1925,51 @@ "node": ">=8" } }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "bin": { + "he": "bin/he" + } + }, + "node_modules/html-to-text": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/html-to-text/-/html-to-text-8.1.0.tgz", + "integrity": "sha512-Z9iYAqYK2c18GswSbnxJSeMs7lyJgwR2oIkDOyOHGBbYsPsG4HvT379jj3Lcbfko8A5ceyyMHAfkmp/BiXA9/Q==", + "dependencies": { + "@selderee/plugin-htmlparser2": "^0.6.0", + "deepmerge": "^4.2.2", + "he": "^1.2.0", + "htmlparser2": "^6.1.0", + "minimist": "^1.2.5", + "selderee": "^0.6.0" + }, + "bin": { + "html-to-text": "bin/cli.js" + }, + "engines": { + "node": ">=10.23.2" + } + }, + "node_modules/htmlparser2": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz", + "integrity": "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==", + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.0.0", + "domutils": "^2.5.2", + "entities": "^2.0.0" + } + }, "node_modules/ignore": { "version": "5.1.8", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", @@ -2131,8 +2281,7 @@ "node_modules/minimist": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", - "dev": true + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" }, "node_modules/mkdirp": { "version": "0.5.5", @@ -2146,6 +2295,11 @@ "mkdirp": "bin/cmd.js" } }, + "node_modules/moo": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/moo/-/moo-0.5.1.tgz", + "integrity": "sha512-I1mnb5xn4fO80BH9BLcF0yLypy2UKl+Cb01Fu0hJRkJjlCRtxZMWkTdAtDd5ZqCOxtCkhmRwyI57vWT+1iZ67w==" + }, "node_modules/mri": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", @@ -2194,6 +2348,27 @@ "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", "dev": true }, + "node_modules/nearley": { + "version": "2.20.1", + "resolved": "https://registry.npmjs.org/nearley/-/nearley-2.20.1.tgz", + "integrity": "sha512-+Mc8UaAebFzgV+KpI5n7DasuuQCHA89dmwm7JXw3TV43ukfNQ9DnBH3Mdb2g/I4Fdxc26pwimBWvjIw0UAILSQ==", + "dependencies": { + "commander": "^2.19.0", + "moo": "^0.5.0", + "railroad-diagrams": "^1.0.0", + "randexp": "0.4.6" + }, + "bin": { + "nearley-railroad": "bin/nearley-railroad.js", + "nearley-test": "bin/nearley-test.js", + "nearley-unparse": "bin/nearley-unparse.js", + "nearleyc": "bin/nearleyc.js" + }, + "funding": { + "type": "individual", + "url": "https://nearley.js.org/#give-to-nearley" + } + }, "node_modules/node-fetch": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.0.0.tgz", @@ -2290,6 +2465,18 @@ "node": ">=6" } }, + "node_modules/parseley": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/parseley/-/parseley-0.7.0.tgz", + "integrity": "sha512-xyOytsdDu077M3/46Am+2cGXEKM9U9QclBDv7fimY7e+BBlxh2JcBp2mgNsmkyA9uvgyTjVzDi7cP1v4hcFxbw==", + "dependencies": { + "moo": "^0.5.1", + "nearley": "^2.20.1" + }, + "funding": { + "url": "https://ko-fi.com/killymxi" + } + }, "node_modules/path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", @@ -2445,6 +2632,23 @@ } ] }, + "node_modules/railroad-diagrams": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/railroad-diagrams/-/railroad-diagrams-1.0.0.tgz", + "integrity": "sha1-635iZ1SN3t+4mcG5Dlc3RVnN234=" + }, + "node_modules/randexp": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/randexp/-/randexp-0.4.6.tgz", + "integrity": "sha512-80WNmd9DA0tmZrw9qQa62GPPWfuXJknrmVmLcxvq4uZBdYqb1wYoKTmnlGUchvVWe0XiLupYkBoXVOxz3C8DYQ==", + "dependencies": { + "discontinuous-range": "1.0.0", + "ret": "~0.1.10" + }, + "engines": { + "node": ">=0.12" + } + }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -2506,6 +2710,14 @@ "node": ">=4" } }, + "node_modules/ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", + "engines": { + "node": ">=0.12" + } + }, "node_modules/reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", @@ -2573,6 +2785,34 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" }, + "node_modules/rss": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/rss/-/rss-1.2.2.tgz", + "integrity": "sha1-UKFpiHYTgTOnT5oF0r3I240nqSE=", + "dependencies": { + "mime-types": "2.1.13", + "xml": "1.0.1" + } + }, + "node_modules/rss/node_modules/mime-db": { + "version": "1.25.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.25.0.tgz", + "integrity": "sha1-wY29fHOl2/b0SgJNwNFloeexw5I=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/rss/node_modules/mime-types": { + "version": "2.1.13", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.13.tgz", + "integrity": "sha1-4HqqnGxrmnyjASxpADrSWjnpKog=", + "dependencies": { + "mime-db": "~1.25.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -2647,6 +2887,17 @@ "node": ">=8.9.0" } }, + "node_modules/selderee": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/selderee/-/selderee-0.6.0.tgz", + "integrity": "sha512-ibqWGV5aChDvfVdqNYuaJP/HnVBhlRGSRrlbttmlMpHcLuTqqbMH36QkSs9GEgj5M88JDYLI8eyP94JaQ8xRlg==", + "dependencies": { + "parseley": "^0.7.0" + }, + "funding": { + "url": "https://ko-fi.com/killymxi" + } + }, "node_modules/semver": { "version": "7.3.5", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", @@ -3233,6 +3484,11 @@ } } }, + "node_modules/xml": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz", + "integrity": "sha1-eLpyAgApxbyHuKgaPPzXS0ovweU=" + }, "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", @@ -3447,6 +3703,15 @@ "integrity": "sha512-Oi4EEi8vOne8RM1tCdQ3kYAtl/J6ztak3Th6wwGFqA2SVNJtedw196LjsLX0bK8Li8cwaljbFf08N+0zeqhkWQ==", "dev": true }, + "@selderee/plugin-htmlparser2": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@selderee/plugin-htmlparser2/-/plugin-htmlparser2-0.6.0.tgz", + "integrity": "sha512-J3jpy002TyBjd4N/p6s+s90eX42H2eRhK3SbsZuvTDv977/E8p2U3zikdiehyJja66do7FlxLomZLPlvl2/xaA==", + "requires": { + "domhandler": "^4.2.0", + "selderee": "^0.6.0" + } + }, "@sindresorhus/is": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.2.0.tgz", @@ -3521,6 +3786,12 @@ "integrity": "sha512-LOnASQoeNZMkzexRuyqcBBDZ6rS+rQxUMkmj5A0PkhhiSZivLIuz6Hxyr1mkGoEZEkk66faROmpMi4fFkrKsBA==", "dev": true }, + "@types/rss": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/rss/-/rss-0.0.29.tgz", + "integrity": "sha512-9hAYKyOcoxWqkJMue6bi0fcNXxjJiRutVvqyImy732LmTCxRz9BYPmSmujG7uddK0ZEP/35K3QVZ0sfAVyPnQw==", + "dev": true + }, "@types/sass": { "version": "1.16.1", "resolved": "https://registry.npmjs.org/@types/sass/-/sass-1.16.1.tgz", @@ -3825,6 +4096,11 @@ "delayed-stream": "~1.0.0" } }, + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -3863,6 +4139,11 @@ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true }, + "deepmerge": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", + "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==" + }, "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -3884,6 +4165,11 @@ "path-type": "^4.0.0" } }, + "discontinuous-range": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/discontinuous-range/-/discontinuous-range-1.0.0.tgz", + "integrity": "sha1-44Mx8IRLukm5qctxx3FYWqsbxlo=" + }, "discord-api-types": { "version": "0.23.1", "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.23.1.tgz", @@ -3926,6 +4212,39 @@ "esutils": "^2.0.2" } }, + "dom-serializer": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.3.2.tgz", + "integrity": "sha512-5c54Bk5Dw4qAxNOI1pFEizPSjVsx5+bpJKmL2kPn8JhBUq2q09tTCa3mjijun2NfK78NMouDYNMBkOrPZiS+ig==", + "requires": { + "domelementtype": "^2.0.1", + "domhandler": "^4.2.0", + "entities": "^2.0.0" + } + }, + "domelementtype": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.2.0.tgz", + "integrity": "sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A==" + }, + "domhandler": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.0.tgz", + "integrity": "sha512-fC0aXNQXqKSFTr2wDNZDhsEYjCiYsDWl3D01kwt25hm1YIPyDGHvvi3rw+PLqHAl/m71MaiF7d5zvBr0p5UB2g==", + "requires": { + "domelementtype": "^2.2.0" + } + }, + "domutils": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", + "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", + "requires": { + "dom-serializer": "^1.0.1", + "domelementtype": "^2.2.0", + "domhandler": "^4.2.0" + } + }, "dot-prop": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-6.0.1.tgz", @@ -3956,6 +4275,11 @@ "ansi-colors": "^4.1.1" } }, + "entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==" + }, "es6-promise": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.3.1.tgz", @@ -4422,6 +4746,11 @@ "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", "dev": true }, + "gfm.css": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/gfm.css/-/gfm.css-1.1.2.tgz", + "integrity": "sha512-KhK3rqxMj+UTLRxWnfUA5n8XZYMWfHrrcCxtWResYR2B3hWIqBM6v9FPGZSlVuX+ScLewizOvNkjYXuPs95ThQ==" + }, "glob": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", @@ -4489,6 +4818,35 @@ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, + "he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==" + }, + "html-to-text": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/html-to-text/-/html-to-text-8.1.0.tgz", + "integrity": "sha512-Z9iYAqYK2c18GswSbnxJSeMs7lyJgwR2oIkDOyOHGBbYsPsG4HvT379jj3Lcbfko8A5ceyyMHAfkmp/BiXA9/Q==", + "requires": { + "@selderee/plugin-htmlparser2": "^0.6.0", + "deepmerge": "^4.2.2", + "he": "^1.2.0", + "htmlparser2": "^6.1.0", + "minimist": "^1.2.5", + "selderee": "^0.6.0" + } + }, + "htmlparser2": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz", + "integrity": "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==", + "requires": { + "domelementtype": "^2.0.1", + "domhandler": "^4.0.0", + "domutils": "^2.5.2", + "entities": "^2.0.0" + } + }, "ignore": { "version": "5.1.8", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", @@ -4731,8 +5089,7 @@ "minimist": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", - "dev": true + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" }, "mkdirp": { "version": "0.5.5", @@ -4743,6 +5100,11 @@ "minimist": "^1.2.5" } }, + "moo": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/moo/-/moo-0.5.1.tgz", + "integrity": "sha512-I1mnb5xn4fO80BH9BLcF0yLypy2UKl+Cb01Fu0hJRkJjlCRtxZMWkTdAtDd5ZqCOxtCkhmRwyI57vWT+1iZ67w==" + }, "mri": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", @@ -4784,6 +5146,17 @@ "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", "dev": true }, + "nearley": { + "version": "2.20.1", + "resolved": "https://registry.npmjs.org/nearley/-/nearley-2.20.1.tgz", + "integrity": "sha512-+Mc8UaAebFzgV+KpI5n7DasuuQCHA89dmwm7JXw3TV43ukfNQ9DnBH3Mdb2g/I4Fdxc26pwimBWvjIw0UAILSQ==", + "requires": { + "commander": "^2.19.0", + "moo": "^0.5.0", + "railroad-diagrams": "^1.0.0", + "randexp": "0.4.6" + } + }, "node-fetch": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.0.0.tgz", @@ -4854,6 +5227,15 @@ "callsites": "^3.0.0" } }, + "parseley": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/parseley/-/parseley-0.7.0.tgz", + "integrity": "sha512-xyOytsdDu077M3/46Am+2cGXEKM9U9QclBDv7fimY7e+BBlxh2JcBp2mgNsmkyA9uvgyTjVzDi7cP1v4hcFxbw==", + "requires": { + "moo": "^0.5.1", + "nearley": "^2.20.1" + } + }, "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", @@ -4952,6 +5334,20 @@ "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", "dev": true }, + "railroad-diagrams": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/railroad-diagrams/-/railroad-diagrams-1.0.0.tgz", + "integrity": "sha1-635iZ1SN3t+4mcG5Dlc3RVnN234=" + }, + "randexp": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/randexp/-/randexp-0.4.6.tgz", + "integrity": "sha512-80WNmd9DA0tmZrw9qQa62GPPWfuXJknrmVmLcxvq4uZBdYqb1wYoKTmnlGUchvVWe0XiLupYkBoXVOxz3C8DYQ==", + "requires": { + "discontinuous-range": "1.0.0", + "ret": "~0.1.10" + } + }, "readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -4995,6 +5391,11 @@ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true }, + "ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==" + }, "reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", @@ -5043,6 +5444,30 @@ } } }, + "rss": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/rss/-/rss-1.2.2.tgz", + "integrity": "sha1-UKFpiHYTgTOnT5oF0r3I240nqSE=", + "requires": { + "mime-types": "2.1.13", + "xml": "1.0.1" + }, + "dependencies": { + "mime-db": { + "version": "1.25.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.25.0.tgz", + "integrity": "sha1-wY29fHOl2/b0SgJNwNFloeexw5I=" + }, + "mime-types": { + "version": "2.1.13", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.13.tgz", + "integrity": "sha1-4HqqnGxrmnyjASxpADrSWjnpKog=", + "requires": { + "mime-db": "~1.25.0" + } + } + } + }, "run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -5093,6 +5518,14 @@ "chokidar": ">=3.0.0 <4.0.0" } }, + "selderee": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/selderee/-/selderee-0.6.0.tgz", + "integrity": "sha512-ibqWGV5aChDvfVdqNYuaJP/HnVBhlRGSRrlbttmlMpHcLuTqqbMH36QkSs9GEgj5M88JDYLI8eyP94JaQ8xRlg==", + "requires": { + "parseley": "^0.7.0" + } + }, "semver": { "version": "7.3.5", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", @@ -5477,6 +5910,11 @@ "dev": true, "requires": {} }, + "xml": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz", + "integrity": "sha1-eLpyAgApxbyHuKgaPPzXS0ovweU=" + }, "yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", diff --git a/package.json b/package.json index 81367ce40..1ccf65a92 100644 --- a/package.json +++ b/package.json @@ -22,14 +22,18 @@ "test:size": "npm i && size-limit" }, "dependencies": { + "gfm.css": "^1.1.2", + "html-to-text": "^8.1.0", "rfs": "^9.0.6", - "rrule": "^2.6.8" + "rrule": "^2.6.8", + "rss": "^1.2.2" }, "devDependencies": { "@size-limit/file": "^7.0.4", "@sveltejs/adapter-static": "next", "@sveltejs/adapter-vercel": "next", "@sveltejs/kit": "next", + "@types/rss": "^0.0.29", "@typescript-eslint/eslint-plugin": "^4.31.1", "@typescript-eslint/parser": "^4.31.1", "axios": "^0.24.0", diff --git a/src/lib/components/sections/navbar.svelte b/src/lib/components/sections/navbar.svelte index 976d05584..53454137c 100644 --- a/src/lib/components/sections/navbar.svelte +++ b/src/lib/components/sections/navbar.svelte @@ -13,6 +13,7 @@ path: '/nodebuds', }, { title: 'connect', path: '/connect' }, + { title: 'blog', path: '/blog' }, ]; const handleClose = () => { @@ -108,7 +109,9 @@ nav .page-paths:hover, nav .page-paths[aria-current='true'], nav .page-connect:hover, - nav .page-connect[aria-current='true'] { + nav .page-connect[aria-current='true'], + nav .page-blog:hover, + nav .page-blog[aria-current='true'] { color: var(--acm-blue); } diff --git a/src/lib/constants/officers.json b/src/lib/constants/officers.json index 36f918e53..cee8ce093 100644 --- a/src/lib/constants/officers.json +++ b/src/lib/constants/officers.json @@ -1,240 +1,241 @@ [ - { - "name": "Jacob Nguyen", - "positions": { - "S21": "President, Create Director, NodeBuds Officer" - }, - "picture": "jacob-nguyen.png" - }, - { - "name": "Aaron Lieberman", - "positions": { - "S21": "Internal Vice President, NodeBuds Officer", - "F21": "President" - }, - "picture": "aaron-lieberman.png" - }, - { - "name": "Karnikaa Velumani", - "positions": { - "F21": "Vice President" - }, - "picture": "karnikaa-velumani.png" - }, - { - "name": "Samuel Sandoval", - "positions": { - "S21": "Vice President, Dev Director" - }, - "picture": "samuel-sandoval.png" - }, - { - "name": "Ethan Davidson", - "positions": { - "F20": "Competition Manager", - "S21": "Webmaster", - "F21": "Webmaster" - }, - "picture": "ethan-davidson.png" - }, - { - "name": "Andrew Lau", - "positions": { - "S21": "Treasurer" - }, - "picture": "andrew-lau.png" - }, - { - "name": "Tommy Le", - "positions": { - "F21": "Treasurer" - }, - "picture": "tommy-le.png" - }, - { - "name": "Jason Anthony", - "positions": { - "S21": "Secretary" - }, - "picture": "jason-anthony.png" - }, - { - "name": "Nicolas Renteria", - "positions": { - "S21": "Marketing Chair, NodeBuds Officer" - }, - "picture": "nicolas-renteria.png" - }, - { - "name": "Joshua Hughes", - "positions": { - "S21": "ICC Representative" - }, - "picture": "joshua-hughes.png" - }, - { - "name": "Wilbert Rodriguez", - "positions": { - "S21": "Intern Program Manager" - }, - "picture": "wilbert-rodriguez.png" - }, - { - "name": "Shaleen Mathur", - "positions": { - "S21": "Co-Workshop Manager" - }, - "picture": "shaleen-mathur.png" - }, - { - "name": "Johnson Tong", - "positions": { - "S21": "Co-Workshop Manager" - }, - "picture": "johnson-tong.png" - }, - { - "name": "Kavit Sanghavi", - "positions": { - "S21": "Algo Director" - }, - "picture": "kavit-sanghavi.png" - }, - { - "name": "Kevin Dillon", - "positions": { - "S21": "Algo Officer" - }, - "picture": "kevin-dillon.png" - }, - { - "name": "Parth Sharma", - "positions": { - "S21": "Algo Officer" - }, - "picture": "parth-sharma.png" - }, - { - "name": "Armanul Ambia", - "positions": { - "S21": "Dev Officer, NodeBuds Officer", - "F21": "Algo Director" - }, - "picture": "armanul-ambia.png" - }, - { - "name": "Alex Truong", - "positions": { - "F21": "Algo Officer" - }, - "picture": "alex-truong.png" - }, - { - "name": "Wesley Chou", - "positions": { - "S21": "Dev Officer", - "F21": "Dev Director" - }, - "picture": "wesley-chou.png" - }, - { - "name": "Andy Lasso", - "positions": { - "F21": "Dev Officer" - }, - "picture": "andy-lasso.png" - }, - { - "name": "Rina Watanabe", - "positions": { - "F21": "Dev Officer, Project Manager" - }, - "picture": "rina-watanabe.png" - }, - { - "name": "Jorge Mejia", - "positions": { - "F21": "Dev Officer" - }, - "picture": "jorge-mejia.png" - }, - { - "name": "Mike Ploythai", - "positions": { - "S21": "Create Officer", - "F21": "Create Director, Marketing Chair" - }, - "picture": "mike-ploythai.png" - }, - { - "name": "Samuel Valls", - "positions": { - "S21": "Community Manager", - "F21": "Create Officer" - }, - "picture": "samuel-valls.png" - }, - { - "name": "Lisa Hong", - "positions": { - "S21": "Create Officer" - }, - "picture": "lisa-hong.png" - }, - { - "name": "Kayla Nguyen", - "positions": { - "F21": "Create Officer" - }, - "picture": "kayla-nguyen.png" - }, - { - "name": "Serena Naranjo", - "positions": { - "F21": "Create Officer" - }, - "picture": "serena-naranjo.png" - }, - { - "name": "Taylor Noh", - "positions": { - "S21": "NodeBuds Officer" - }, - "picture": "taylor-noh.png" - }, - { - "name": "Eugene Lee", - "positions": { - "S21": "NodeBuds Officer" - }, - "picture": "eugene-lee.png" - }, - { - "name": "Sami Bajwa", - "positions": { - "F21": "NodeBuds Officer" - }, - "picture": "sami-bajwa.png" - }, - { - "name": "Eduardo Gomez", - "positions": { - "S21": "NodeBuds Officer" - }, - "picture": "eduardo-gomez.png" - }, - { - "name": "Ean McGilvery", - "positions": { - "S21": "NodeBuds Officer" - }, - "picture": "ean-mcgilvery.png" - }, - { - "name": "Dalisa Nguyen", - "positions": { - "S21": "NodeBuds Officer" - }, - "picture": "dalisa-nguyen.png" - } + { + "name": "Jacob Nguyen", + "positions": { + "S21": "President, Create Director, NodeBuds Officer" + }, + "picture": "jacob-nguyen.png" + }, + { + "name": "Aaron Lieberman", + "positions": { + "S21": "Internal Vice President, NodeBuds Officer", + "F21": "President" + }, + "picture": "aaron-lieberman.png" + }, + { + "name": "Karnikaa Velumani", + "positions": { + "F21": "Vice President" + }, + "picture": "karnikaa-velumani.png" + }, + { + "name": "Samuel Sandoval", + "positions": { + "S21": "Vice President, Dev Director" + }, + "picture": "samuel-sandoval.png" + }, + { + "name": "Ethan Davidson", + "positions": { + "F20": "Competition Manager", + "S21": "Webmaster", + "F21": "Webmaster" + }, + "picture": "ethan-davidson.png", + "ghUsername": "EthanThatOneKid" + }, + { + "name": "Andrew Lau", + "positions": { + "S21": "Treasurer" + }, + "picture": "andrew-lau.png" + }, + { + "name": "Tommy Le", + "positions": { + "F21": "Treasurer" + }, + "picture": "tommy-le.png" + }, + { + "name": "Jason Anthony", + "positions": { + "S21": "Secretary" + }, + "picture": "jason-anthony.png" + }, + { + "name": "Nicolas Renteria", + "positions": { + "S21": "Marketing Chair, NodeBuds Officer" + }, + "picture": "nicolas-renteria.png" + }, + { + "name": "Joshua Hughes", + "positions": { + "S21": "ICC Representative" + }, + "picture": "joshua-hughes.png" + }, + { + "name": "Wilbert Rodriguez", + "positions": { + "S21": "Intern Program Manager" + }, + "picture": "wilbert-rodriguez.png" + }, + { + "name": "Shaleen Mathur", + "positions": { + "S21": "Co-Workshop Manager" + }, + "picture": "shaleen-mathur.png" + }, + { + "name": "Johnson Tong", + "positions": { + "S21": "Co-Workshop Manager" + }, + "picture": "johnson-tong.png" + }, + { + "name": "Kavit Sanghavi", + "positions": { + "S21": "Algo Director" + }, + "picture": "kavit-sanghavi.png" + }, + { + "name": "Kevin Dillon", + "positions": { + "S21": "Algo Officer" + }, + "picture": "kevin-dillon.png" + }, + { + "name": "Parth Sharma", + "positions": { + "S21": "Algo Officer" + }, + "picture": "parth-sharma.png" + }, + { + "name": "Armanul Ambia", + "positions": { + "S21": "Dev Officer, NodeBuds Officer", + "F21": "Algo Director" + }, + "picture": "armanul-ambia.png" + }, + { + "name": "Alex Truong", + "positions": { + "F21": "Algo Officer" + }, + "picture": "alex-truong.png" + }, + { + "name": "Wesley Chou", + "positions": { + "S21": "Dev Officer", + "F21": "Dev Director" + }, + "picture": "wesley-chou.png" + }, + { + "name": "Andy Lasso", + "positions": { + "F21": "Dev Officer" + }, + "picture": "andy-lasso.png" + }, + { + "name": "Rina Watanabe", + "positions": { + "F21": "Dev Officer, Project Manager" + }, + "picture": "rina-watanabe.png" + }, + { + "name": "Jorge Mejia", + "positions": { + "F21": "Dev Officer" + }, + "picture": "jorge-mejia.png" + }, + { + "name": "Mike Ploythai", + "positions": { + "S21": "Create Officer", + "F21": "Create Director, Marketing Chair" + }, + "picture": "mike-ploythai.png" + }, + { + "name": "Samuel Valls", + "positions": { + "S21": "Community Manager", + "F21": "Create Officer" + }, + "picture": "samuel-valls.png" + }, + { + "name": "Lisa Hong", + "positions": { + "S21": "Create Officer" + }, + "picture": "lisa-hong.png" + }, + { + "name": "Kayla Nguyen", + "positions": { + "F21": "Create Officer" + }, + "picture": "kayla-nguyen.png" + }, + { + "name": "Serena Naranjo", + "positions": { + "F21": "Create Officer" + }, + "picture": "serena-naranjo.png" + }, + { + "name": "Taylor Noh", + "positions": { + "S21": "NodeBuds Officer" + }, + "picture": "taylor-noh.png" + }, + { + "name": "Eugene Lee", + "positions": { + "S21": "NodeBuds Officer" + }, + "picture": "eugene-lee.png" + }, + { + "name": "Sami Bajwa", + "positions": { + "F21": "NodeBuds Officer" + }, + "picture": "sami-bajwa.png" + }, + { + "name": "Eduardo Gomez", + "positions": { + "S21": "NodeBuds Officer" + }, + "picture": "eduardo-gomez.png" + }, + { + "name": "Ean McGilvery", + "positions": { + "S21": "NodeBuds Officer" + }, + "picture": "ean-mcgilvery.png" + }, + { + "name": "Dalisa Nguyen", + "positions": { + "S21": "NodeBuds Officer" + }, + "picture": "dalisa-nguyen.png" + } ] diff --git a/src/lib/constants/officers.ts b/src/lib/constants/officers.ts index eae6a6b41..d41f69c77 100644 --- a/src/lib/constants/officers.ts +++ b/src/lib/constants/officers.ts @@ -5,6 +5,7 @@ export interface Officer { positions: Record; picture?: string; url?: string; + ghUsername?: string; } export const TERM_FALL_20 = 'F20'; diff --git a/src/routes/newsletters/[id].json.ts b/src/routes/blog/[id].json.ts similarity index 70% rename from src/routes/newsletters/[id].json.ts rename to src/routes/blog/[id].json.ts index ace0d0fe8..9ea95159e 100644 --- a/src/routes/newsletters/[id].json.ts +++ b/src/routes/blog/[id].json.ts @@ -1,13 +1,18 @@ -import type { EndpointOutput } from '@sveltejs/kit'; +import type { EndpointOutput, IncomingRequest } from '@sveltejs/kit'; import type { DefaultBody } from '@sveltejs/kit/types/endpoint'; +interface ServerRequest extends IncomingRequest { + params: { + id: number; + }; +} + const getCache = async (id: number, baseURL: string) => { - const target = baseURL + `/newsletters.json`; + const target = baseURL + `/blog.json`; try { const response = await fetch(target); const data = await response.json(); const newsletter = [...data].find((item) => item.id === id); - console.log({ id, data, newsletter }); return newsletter; } catch (err) { console.error(err); @@ -15,7 +20,7 @@ const getCache = async (id: number, baseURL: string) => { } }; -export async function get(request): Promise { +export async function get(request: ServerRequest): Promise { const id = Number(request.params.id); const base = `http://${request.host}`; const body = (await getCache(id, base)) as unknown as DefaultBody; diff --git a/src/routes/blog/[id].svelte b/src/routes/blog/[id].svelte new file mode 100644 index 000000000..d12644dba --- /dev/null +++ b/src/routes/blog/[id].svelte @@ -0,0 +1,100 @@ + + + + + + {post.title} + + + + + +
+

{post.title}

+ + + +

+ by + + @{post.author.displayname} + +

+ + + +
+
+ {@html post.html} +
+ + Tags: {post.labels.join(', ')} +
+ + +
+ + diff --git a/src/routes/blog/[id].txt.ts b/src/routes/blog/[id].txt.ts new file mode 100644 index 000000000..22b7b340c --- /dev/null +++ b/src/routes/blog/[id].txt.ts @@ -0,0 +1,57 @@ +import type { EndpointOutput, IncomingRequest } from '@sveltejs/kit'; +import { convert as convertHtml2Txt } from 'html-to-text'; +import type { Newsletter } from './_query'; + +interface ServerRequest extends IncomingRequest { + params: { + id: number; + }; +} + +function serializeNewsletter(newsletter: Newsletter) { + const txtTitle = convertHtml2Txt(newsletter.title); + + return [ + `---`, + `id: ${newsletter.id}`, + `title: "${txtTitle}"`, + `author: "${newsletter.author.displayname}` + + (newsletter.author.url !== undefined ? ` (${newsletter.author.url})"` : '"'), + `labels: [${newsletter.labels.map((l) => `"${l}"`).join(', ')}]`, + `date: "${new Date(newsletter.lastEdited).toISOString()}"`, + `---`, + '', + txtTitle, + '='.repeat(txtTitle.length), + '', + convertHtml2Txt(newsletter.html, { wordwrap: 100 }), + ].join('\n'); +} + +// Query the newsletter endpoint itself instead of the lib in order to +// take advantage of caching. +async function getCache(id: number, baseURL: string) { + const target = baseURL + `/newsletters.json`; + try { + const response = await fetch(target); + const data = await response.json(); + const newsletter: Newsletter = [...data].find((item) => item.id === id); + return newsletter; + } catch (err) { + console.error(err); + return undefined; + } +} + +export async function get(request: ServerRequest): Promise { + const id = Number(request.params.id); + const base = `http://${request.host}`; + const newsletter = await getCache(id, base); + + if (!newsletter) { + return { body: '', status: 404, headers: { 'Content-Type': 'text/plain' } }; + } + + const body = serializeNewsletter(newsletter); + return { body, status: 200, headers: { 'Content-Type': 'text/plain' } }; +} diff --git a/src/routes/blog/_query.ts b/src/routes/blog/_query.ts new file mode 100644 index 000000000..7d98be4b2 --- /dev/null +++ b/src/routes/blog/_query.ts @@ -0,0 +1,84 @@ +import type { Officer } from '$lib/constants'; +import { OFFICERS } from '$lib/constants'; + +export interface Newsletter { + id: string; + url: string; + discussionUrl: string; + title: string; + html: string; + lastEdited: number | null; + labels: string[]; + author: { + displayname: string; + url: string; + picture: string; + }; +} + +// @see https://docs.github.com/en/graphql/overview/explorer +export const newslettersQuery = `{ + repository(owner: "ethanthatonekid", name: "acmcsuf.com") { + discussions(first: 100, categoryId: "${import.meta.env.VITE_GH_DISCUSSION_CATEGORY_ID}") { + nodes { + title + url + number + bodyHTML + createdAt + lastEditedAt + + author { + login + url + avatarUrl + } + + labels(first: 100) { + nodes { + name + } + } + } + } + } +}`; + +function getOfficerByGhUsername(ghUsername: string): Officer | null { + // get author by GitHub username + const officer = OFFICERS.find((o) => o.ghUsername !== undefined && o.ghUsername === ghUsername); + return officer ?? null; +} + +function formatNewsletters(output): Newsletter[] { + const discussions = output.data.repository.discussions.nodes; + return discussions.map( + (discussion: any): Newsletter => ({ + id: discussion.number, + url: `https://acmcsuf.com/blog/${discussion.number as string}`, + discussionUrl: discussion.url, + title: discussion.title, + html: discussion.bodyHTML, + lastEdited: discussion.lastEditedAt ?? discussion.createdAt, + labels: discussion.labels.nodes.map(({ name }) => name), + author: { + displayname: discussion.author.login, + url: discussion.author.url, + picture: discussion.author.avatarUrl, + }, + }) + ); +} + +export async function fetchNewsletters(): Promise { + const ghAccessToken = import.meta.env.VITE_GH_ACCESS_TOKEN; + + const response = await fetch('https://api.github.com/graphql', { + method: 'POST', + headers: { Authorization: `token ${ghAccessToken}`, 'Content-Type': 'application/json' }, + body: JSON.stringify({ query: newslettersQuery }), + }); + + const newsletters = formatNewsletters(await response.json()); + return newsletters; +} diff --git a/src/routes/blog/index.json.ts b/src/routes/blog/index.json.ts new file mode 100644 index 000000000..c9b32d73d --- /dev/null +++ b/src/routes/blog/index.json.ts @@ -0,0 +1,12 @@ +import type { EndpointOutput } from '@sveltejs/kit'; +import type { DefaultBody } from '@sveltejs/kit/types/endpoint'; +import { fetchNewsletters } from './_query'; + +export async function get(): Promise { + return { + body: (await fetchNewsletters()) as unknown as DefaultBody, + headers: { + 'content-type': 'application/json', + }, + }; +} diff --git a/src/routes/blog/index.svelte b/src/routes/blog/index.svelte new file mode 100644 index 000000000..d81bc9da7 --- /dev/null +++ b/src/routes/blog/index.svelte @@ -0,0 +1,113 @@ + + + + + + +
+ README by acmCSUF + +

The official acmCSUF blog.

+ + + + +
+ + + + + diff --git a/src/routes/blog/index.xml.ts b/src/routes/blog/index.xml.ts new file mode 100644 index 000000000..ebb0f0353 --- /dev/null +++ b/src/routes/blog/index.xml.ts @@ -0,0 +1,66 @@ +import type { EndpointOutput } from '@sveltejs/kit'; +import type { ResponseHeaders } from '@sveltejs/kit/types/helper'; +import RSS from 'rss'; +import type { Newsletter } from './_query'; +import { fetchNewsletters } from './_query'; + +function getCategories(posts: Newsletter[]): string[] { + const categories = new Set(); + + for (const newsletter of posts) { + for (const label of newsletter.labels) { + categories.add(label); + } + } + + return Array.from(categories); +} + +function truncateDescription(description: string, length: number) { + const indexOfEndOfLastWord = description.lastIndexOf(' ', length); + return description.slice(0, indexOfEndOfLastWord); +} + +function makeRssFeed(posts: Newsletter[]): string { + const feed = new RSS({ + title: 'acmCSUF Community Newsletters', + description: + 'Posts written about computer science topics by the ACM community at California State Univerity Fullerton. It covers all the latest news and events for acmCSUF.', + feed_url: 'https://acmcsuf.com/newsletters.xml', + site_url: 'https://acmcsuf.com', + image_url: 'https://acmcsuf.com/assets/png/acm-csuf-badge.png', + categories: getCategories(posts), + pubDate: 'Jan 24, 2022 08:00:00 GMT', + docs: 'https://acmcsuf.com/history-of-rss', + copyright: '2022 acmCSUF', + language: 'en', + managingEditor: 'mikeploythai', + webMaster: 'EthanThatOneKid', + ttl: 60, + }); + + for (const newsletter of posts) { + feed.item({ + title: newsletter.title, + description: truncateDescription(newsletter.html, /* length=*/ 250), + url: newsletter.url, + categories: newsletter.labels, + author: newsletter.author.displayname, + date: new Date(newsletter.lastEdited).toISOString(), + }); + } + + return feed.xml({ indent: ' '.repeat(2) }); +} + +export async function get(): Promise { + const headers: ResponseHeaders = { + 'Cache-Control': 'max-age=0, s-maxage=3600', + 'Content-Type': 'application/xml', + }; + + const posts = await fetchNewsletters(); + const body = makeRssFeed(posts); + + return { headers, body }; +} diff --git a/src/routes/events.svelte b/src/routes/events.svelte index fc23a6f01..51fe5c647 100644 --- a/src/routes/events.svelte +++ b/src/routes/events.svelte @@ -35,7 +35,7 @@

- +

This week's events 📅

diff --git a/src/routes/newsletters/[id].svelte b/src/routes/newsletters/[id].svelte deleted file mode 100644 index 742731dc1..000000000 --- a/src/routes/newsletters/[id].svelte +++ /dev/null @@ -1,20 +0,0 @@ - - - - -

Newsletter!

- -
{JSON.stringify(posts, null, 2)}
diff --git a/src/routes/newsletters/_query.ts b/src/routes/newsletters/_query.ts deleted file mode 100644 index 5c7528196..000000000 --- a/src/routes/newsletters/_query.ts +++ /dev/null @@ -1,40 +0,0 @@ -const id = import.meta.env.VITE_GH_DISCUSSION_CATEGORY_ID; - -export interface Newsletter { - id: number; - title: string; - html: string; - lastEdited: number | null; - labels: string[]; - author: { - displayname: string; - url: string; - picture: string; - }; -} - -// @see https://docs.github.com/en/graphql/overview/explorer -export const newslettersQuery = `{ - repository(owner: "ethanthatonekid", name: "acmcsuf.com") { - discussions(first: 100, categoryId: "${id}") { - nodes { - title - number - bodyHTML - lastEditedAt - - author { - login - url - avatarUrl - } - - labels(first: 100) { - nodes { - name - } - } - } - } - } -}`; diff --git a/src/routes/newsletters/index.json.ts b/src/routes/newsletters/index.json.ts deleted file mode 100644 index 4ed61416c..000000000 --- a/src/routes/newsletters/index.json.ts +++ /dev/null @@ -1,46 +0,0 @@ -import type { EndpointOutput } from '@sveltejs/kit'; -import type { DefaultBody } from '@sveltejs/kit/types/endpoint'; -import type { Newsletter } from './_query'; -import { newslettersQuery } from './_query'; - -export const cache = new Map(); - -const getCache = async () => { - const ghAccessToken = import.meta.env.VITE_GH_ACCESS_TOKEN; - const response = await fetch('https://api.github.com/graphql', { - method: 'POST', - headers: { Authorization: `token ${ghAccessToken}`, 'Content-Type': 'application/json' }, - body: JSON.stringify({ query: newslettersQuery }), - }); - const newsletters = formatNewsletters(await response.json()); - newsletters.forEach((newsletter: Newsletter) => cache.set(newsletter.id, newsletter)); - return newsletters; -}; - -const formatNewsletters = (output: any): Newsletter[] => { - console.log(JSON.stringify(output, null, 2)); - const discussions = output.data.repository.discussions.nodes; - return discussions.map( - (discussion: any): Newsletter => ({ - id: discussion.number, - title: discussion.title, - html: discussion.bodyHTML, - lastEdited: discussion.lastEditedAt, - labels: discussion.labels.nodes.map(({ name }) => name), - author: { - displayname: discussion.author.login, - url: discussion.author.url, - picture: discussion.author.avatarUrl, - }, - }) - ); -}; - -export async function get(): Promise { - return { - body: (await getCache()) as unknown as DefaultBody, - headers: { - 'content-type': 'application/json', - }, - }; -} diff --git a/src/routes/newsletters/index.svelte b/src/routes/newsletters/index.svelte deleted file mode 100644 index f5d12780d..000000000 --- a/src/routes/newsletters/index.svelte +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - -
-

Newsletters!

- -
- -
{JSON.stringify(posts, null, 2)}
diff --git a/static/assets/readme-logomark.svg b/static/assets/readme-logomark.svg new file mode 100644 index 000000000..2d74aeebf --- /dev/null +++ b/static/assets/readme-logomark.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/static/global.css b/static/global.css index 82f80256a..6d641916e 100644 --- a/static/global.css +++ b/static/global.css @@ -67,6 +67,10 @@ body { } } +.ita { + font-style: italic; +} + /* Branding Macros */ .brand-em { /* `em` is short for "emphasis" */ diff --git a/svelte.config.js b/svelte.config.js index 5fbb8eed5..450644e11 100644 --- a/svelte.config.js +++ b/svelte.config.js @@ -11,6 +11,13 @@ const config = { // hydrate the
element in src/app.html target: '#svelte', adapter: adapter(), + prerender: { + /** @type {import('@sveltejs/kit').PrerenderErrorHandler} */ + onError: ({ status, path, referrer, referenceType }) => { + if (path.startsWith('/blog')) throw new Error('Missing a newsletter!'); + console.warn(`${status} ${path}${referrer ? ` (${referenceType} from ${referrer})` : ''}`); + }, + }, }, };