diff --git a/.prettierrc b/.prettierrc index 90509d3..d0bb80a 100644 --- a/.prettierrc +++ b/.prettierrc @@ -8,5 +8,5 @@ "bracketSpacing": true, "jsxBracketSameLine": true, "arrowParens": "avoid", - "printWidth": 100 + "printWidth": 80 } diff --git a/blog.html b/blog.html new file mode 100644 index 0000000..ba6ccc8 --- /dev/null +++ b/blog.html @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + MKRhere blog + + + + +
+ + + + \ No newline at end of file diff --git a/package.json b/package.json index 392ff6d..1d46a63 100644 --- a/package.json +++ b/package.json @@ -2,10 +2,12 @@ "name": "pw2", "version": "0.1.0", "private": true, + "type": "module", "scripts": { "start": "vite", "build": "tsc && vite build", - "serve": "vite preview" + "serve": "vite preview", + "blog": "node scripts/blog.js" }, "eslintConfig": { "extends": [ @@ -16,17 +18,19 @@ "@emotion/css": "^11.9.0", "date-fns": "^2.28.0", "framer-motion": "^6.3.15", + "marked": "^4.0.17", "react": "^18.2.0", "react-dom": "^18.2.0", "react-router-dom": "^6.3.0" }, "devDependencies": { "@svgr/rollup": "^6.2.1", + "@types/marked": "^4.0.3", + "@types/node": "^18.0.0", "@types/react": "^18.0.14", "@types/react-dom": "^18.0.5", "@types/react-router-dom": "^5.3.3", "@vitejs/plugin-react": "^1.3.2", - "@vitejs/plugin-react-refresh": "^1.3.6", "react-scripts": "5.0.1", "typescript": "^4.7.4", "vite": "^2.9.12" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a2b7ef6..a13c58e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -3,13 +3,15 @@ lockfileVersion: 5.4 specifiers: '@emotion/css': ^11.9.0 '@svgr/rollup': ^6.2.1 + '@types/marked': ^4.0.3 + '@types/node': ^18.0.0 '@types/react': ^18.0.14 '@types/react-dom': ^18.0.5 '@types/react-router-dom': ^5.3.3 '@vitejs/plugin-react': ^1.3.2 - '@vitejs/plugin-react-refresh': ^1.3.6 date-fns: ^2.28.0 framer-motion: ^6.3.15 + marked: ^4.0.17 react: ^18.2.0 react-dom: ^18.2.0 react-router-dom: ^6.3.0 @@ -21,17 +23,19 @@ dependencies: '@emotion/css': 11.9.0 date-fns: 2.28.0 framer-motion: 6.3.15_biqbaboplfbrettd7655fr4n2y + marked: 4.0.17 react: 18.2.0 react-dom: 18.2.0_react@18.2.0 react-router-dom: 6.3.0_biqbaboplfbrettd7655fr4n2y devDependencies: '@svgr/rollup': 6.2.1 + '@types/marked': 4.0.3 + '@types/node': 18.0.0 '@types/react': 18.0.14 '@types/react-dom': 18.0.5 '@types/react-router-dom': 5.3.3 '@vitejs/plugin-react': 1.3.2 - '@vitejs/plugin-react-refresh': 1.3.6 react-scripts: 5.0.1_qtbnez4q7bzoc4eqybg3efzzxe typescript: 4.7.4 vite: 2.9.12 @@ -69,29 +73,6 @@ packages: engines: {node: '>=6.9.0'} dev: true - /@babel/core/7.14.8: - resolution: {integrity: sha512-/AtaeEhT6ErpDhInbXmjHcUQXH0L0TEgscfcxk1qbOvLuKCa5aZT0SOOtDKFY96/CLROwbLSKyFor6idgNaU4Q==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/code-frame': 7.16.7 - '@babel/generator': 7.18.2 - '@babel/helper-compilation-targets': 7.14.5_@babel+core@7.14.8 - '@babel/helper-module-transforms': 7.14.8 - '@babel/helpers': 7.14.8 - '@babel/parser': 7.18.5 - '@babel/template': 7.16.7 - '@babel/traverse': 7.18.5 - '@babel/types': 7.18.4 - convert-source-map: 1.8.0 - debug: 4.3.2 - gensync: 1.0.0-beta.2 - json5: 2.2.0 - semver: 6.3.0 - source-map: 0.5.7 - transitivePeerDependencies: - - supports-color - dev: true - /@babel/core/7.18.5: resolution: {integrity: sha512-MGY8vg3DxMnctw0LdvSEojOsumc70g0t18gNyUdAZqB1Rpd1Bqo/svHGvt+UJ6JcGX+DIekGFDxxIWofBxLCnQ==} engines: {node: '>=6.9.0'} @@ -153,19 +134,6 @@ packages: '@babel/types': 7.18.4 dev: true - /@babel/helper-compilation-targets/7.14.5_@babel+core@7.14.8: - resolution: {integrity: sha512-v+QtZqXEiOnpO6EYvlImB6zCD2Lel06RzOPzmkz/D/XgQiUu3C/Jb1LOqSt/AIA34TYi/Q+KlT8vTQrgdxkbLw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - dependencies: - '@babel/compat-data': 7.18.5 - '@babel/core': 7.14.8 - '@babel/helper-validator-option': 7.16.7 - browserslist: 4.21.0 - semver: 6.3.0 - dev: true - /@babel/helper-compilation-targets/7.18.2_@babel+core@7.18.5: resolution: {integrity: sha512-s1jnPotJS9uQnzFtiZVBUxe67CuBa679oWFHpxYYnTpRL/1ffhyX44R9uYiXoa/pLXcY9H2moJta0iaanlk/rQ==} engines: {node: '>=6.9.0'} @@ -266,22 +234,6 @@ packages: dependencies: '@babel/types': 7.18.4 - /@babel/helper-module-transforms/7.14.8: - resolution: {integrity: sha512-RyE+NFOjXn5A9YU1dkpeBaduagTlZ0+fccnIcAGbv1KGUlReBj7utF7oEth8IdIBQPcux0DDgW5MFBH2xu9KcA==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/helper-module-imports': 7.16.7 - '@babel/helper-replace-supers': 7.18.2 - '@babel/helper-simple-access': 7.14.8 - '@babel/helper-split-export-declaration': 7.16.7 - '@babel/helper-validator-identifier': 7.16.7 - '@babel/template': 7.16.7 - '@babel/traverse': 7.18.5 - '@babel/types': 7.18.4 - transitivePeerDependencies: - - supports-color - dev: true - /@babel/helper-module-transforms/7.18.0: resolution: {integrity: sha512-kclUYSUBIjlvnzN2++K9f2qzYKFgjmnmjwL4zlmU5f8ZtzgWe8s0rUPSTGy2HmK4P8T52MQsS+HTQAgZd3dMEA==} engines: {node: '>=6.9.0'} @@ -333,13 +285,6 @@ packages: - supports-color dev: true - /@babel/helper-simple-access/7.14.8: - resolution: {integrity: sha512-TrFN4RHh9gnWEU+s7JloIho2T76GPwRHhdzOWLqTrMnlas8T9O7ec+oEDNsRXndOmru9ymH9DFrEOxpzPoSbdg==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.18.4 - dev: true - /@babel/helper-simple-access/7.18.2: resolution: {integrity: sha512-7LIrjYzndorDY88MycupkpQLKS1AFfsVRm2k/9PtKScSy5tZq0McZTj+DiMRynboZfIqOKvo03pmhTaUgiD6fQ==} engines: {node: '>=6.9.0'} @@ -387,17 +332,6 @@ packages: - supports-color dev: true - /@babel/helpers/7.14.8: - resolution: {integrity: sha512-ZRDmI56pnV+p1dH6d+UN6GINGz7Krps3+270qqI9UJ4wxYThfAIcI5i7j5vXC4FJ3Wap+S9qcebxeYiqn87DZw==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/template': 7.16.7 - '@babel/traverse': 7.18.5 - '@babel/types': 7.18.4 - transitivePeerDependencies: - - supports-color - dev: true - /@babel/helpers/7.18.2: resolution: {integrity: sha512-j+d+u5xT5utcQSzrh9p+PaJX94h++KN+ng9b9WEJq7pkUPAd61FGqhjuUEdfknb3E/uDBb7ruwEeKkIxNJPIrg==} engines: {node: '>=6.9.0'} @@ -1183,16 +1117,6 @@ packages: '@babel/plugin-transform-react-jsx': 7.17.12_@babel+core@7.18.5 dev: true - /@babel/plugin-transform-react-jsx-self/7.14.5_@babel+core@7.14.8: - resolution: {integrity: sha512-M/fmDX6n0cfHK/NLTcPmrfVAORKDhK8tyjDhyxlUjYyPYYO8FRWwuxBA3WBx8kWN/uBUuwGa3s/0+hQ9JIN3Tg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.14.8 - '@babel/helper-plugin-utils': 7.17.12 - dev: true - /@babel/plugin-transform-react-jsx-self/7.17.12_@babel+core@7.18.5: resolution: {integrity: sha512-7S9G2B44EnYOx74mue02t1uD8ckWZ/ee6Uz/qfdzc35uWHX5NgRy9i+iJSb2LFRgMd+QV9zNcStQaazzzZ3n3Q==} engines: {node: '>=6.9.0'} @@ -1203,16 +1127,6 @@ packages: '@babel/helper-plugin-utils': 7.17.12 dev: true - /@babel/plugin-transform-react-jsx-source/7.14.5_@babel+core@7.14.8: - resolution: {integrity: sha512-1TpSDnD9XR/rQ2tzunBVPThF5poaYT9GqP+of8fAtguYuI/dm2RkrMBDemsxtY0XBzvW7nXjYM0hRyKX9QYj7Q==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.14.8 - '@babel/helper-plugin-utils': 7.17.12 - dev: true - /@babel/plugin-transform-react-jsx-source/7.16.7_@babel+core@7.18.5: resolution: {integrity: sha512-rONFiQz9vgbsnaMtQlZCjIRwhJvlrPET8TabIUK2hzlXw9B9s2Ieaxte1SCOOXMbWRHodbKixNf3BLcWVOQ8Bw==} engines: {node: '>=6.9.0'} @@ -1887,7 +1801,7 @@ packages: engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} dependencies: '@jest/types': 27.5.1 - '@types/node': 16.4.0 + '@types/node': 18.0.0 chalk: 4.1.2 jest-message-util: 27.5.1 jest-util: 27.5.1 @@ -1899,7 +1813,7 @@ packages: engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} dependencies: '@jest/types': 28.1.1 - '@types/node': 16.4.0 + '@types/node': 18.0.0 chalk: 4.1.2 jest-message-util: 28.1.1 jest-util: 28.1.1 @@ -1920,7 +1834,7 @@ packages: '@jest/test-result': 27.5.1 '@jest/transform': 27.5.1 '@jest/types': 27.5.1 - '@types/node': 16.4.0 + '@types/node': 18.0.0 ansi-escapes: 4.3.2 chalk: 4.1.2 emittery: 0.8.1 @@ -1957,7 +1871,7 @@ packages: dependencies: '@jest/fake-timers': 27.5.1 '@jest/types': 27.5.1 - '@types/node': 16.4.0 + '@types/node': 18.0.0 jest-mock: 27.5.1 dev: true @@ -1967,7 +1881,7 @@ packages: dependencies: '@jest/types': 27.5.1 '@sinonjs/fake-timers': 8.1.0 - '@types/node': 16.4.0 + '@types/node': 18.0.0 jest-message-util: 27.5.1 jest-mock: 27.5.1 jest-util: 27.5.1 @@ -1996,7 +1910,7 @@ packages: '@jest/test-result': 27.5.1 '@jest/transform': 27.5.1 '@jest/types': 27.5.1 - '@types/node': 16.4.0 + '@types/node': 18.0.0 chalk: 4.1.2 collect-v8-coverage: 1.0.1 exit: 0.1.2 @@ -2097,7 +2011,7 @@ packages: dependencies: '@types/istanbul-lib-coverage': 2.0.3 '@types/istanbul-reports': 3.0.1 - '@types/node': 16.4.0 + '@types/node': 18.0.0 '@types/yargs': 16.0.4 chalk: 4.1.2 dev: true @@ -2109,7 +2023,7 @@ packages: '@jest/schemas': 28.0.2 '@types/istanbul-lib-coverage': 2.0.3 '@types/istanbul-reports': 3.0.1 - '@types/node': 16.4.0 + '@types/node': 18.0.0 '@types/yargs': 17.0.10 chalk: 4.1.2 dev: true @@ -2278,14 +2192,6 @@ packages: rollup: 2.75.7 dev: true - /@rollup/pluginutils/4.1.1: - resolution: {integrity: sha512-clDjivHqWGXi7u+0d2r2sBi4Ie6VLEAzWMIkvJLnDmxoOhBYOTfzGbOQBA32THHm11/LiJbd01tJUpJsbshSWQ==} - engines: {node: '>= 8.0.0'} - dependencies: - estree-walker: 2.0.2 - picomatch: 2.3.0 - dev: true - /@rollup/pluginutils/4.2.1: resolution: {integrity: sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==} engines: {node: '>= 8.0.0'} @@ -2627,26 +2533,26 @@ packages: resolution: {integrity: sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==} dependencies: '@types/connect': 3.4.35 - '@types/node': 16.4.0 + '@types/node': 18.0.0 dev: true /@types/bonjour/3.5.10: resolution: {integrity: sha512-p7ienRMiS41Nu2/igbJxxLDWrSZ0WxM8UQgCeO9KhoVF7cOVFkrKsiDr1EsJIla8vV3oEEjGcz11jc5yimhzZw==} dependencies: - '@types/node': 16.4.0 + '@types/node': 18.0.0 dev: true /@types/connect-history-api-fallback/1.3.5: resolution: {integrity: sha512-h8QJa8xSb1WD4fpKBDcATDNGXghFj6/3GRWG6dhmRcu0RX1Ubasur2Uvx5aeEwlf0MwblEC2bMzzMQntxnw/Cw==} dependencies: '@types/express-serve-static-core': 4.17.29 - '@types/node': 16.4.0 + '@types/node': 18.0.0 dev: true /@types/connect/3.4.35: resolution: {integrity: sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==} dependencies: - '@types/node': 16.4.0 + '@types/node': 18.0.0 dev: true /@types/eslint-scope/3.7.3: @@ -2674,7 +2580,7 @@ packages: /@types/express-serve-static-core/4.17.29: resolution: {integrity: sha512-uMd++6dMKS32EOuw1Uli3e3BPgdLIXmezcfHv7N4c1s3gkhikBplORPpMq3fuWkxncZN1reb16d5n8yhQ80x7Q==} dependencies: - '@types/node': 16.4.0 + '@types/node': 18.0.0 '@types/qs': 6.9.7 '@types/range-parser': 1.2.4 dev: true @@ -2691,7 +2597,7 @@ packages: /@types/graceful-fs/4.1.5: resolution: {integrity: sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw==} dependencies: - '@types/node': 16.4.0 + '@types/node': 18.0.0 dev: true /@types/history/4.7.11: @@ -2705,7 +2611,7 @@ packages: /@types/http-proxy/1.17.9: resolution: {integrity: sha512-QsbSjA/fSk7xB+UXlCT3wHBy5ai9wOcNDWwZAtud+jXhwOM3l+EYZh8Lng4+/6n8uar0J7xILzqftJdJ/Wdfkw==} dependencies: - '@types/node': 16.4.0 + '@types/node': 18.0.0 dev: true /@types/istanbul-lib-coverage/2.0.3: @@ -2736,12 +2642,16 @@ packages: resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} dev: true + /@types/marked/4.0.3: + resolution: {integrity: sha512-HnMWQkLJEf/PnxZIfbm0yGJRRZYYMhb++O9M36UCTA9z53uPvVoSlAwJr3XOpDEryb7Hwl1qAx/MV6YIW1RXxg==} + dev: true + /@types/mime/1.3.2: resolution: {integrity: sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==} dev: true - /@types/node/16.4.0: - resolution: {integrity: sha512-HrJuE7Mlqcjj+00JqMWpZ3tY8w7EUd+S0U3L1+PQSWiXZbOgyQDvi+ogoUxaHApPJq5diKxYBQwA3iIlNcPqOg==} + /@types/node/18.0.0: + resolution: {integrity: sha512-cHlGmko4gWLVI27cGJntjs/Sj8th9aYwplmZFwmmgYQQvL5NUsgVJG7OddLvNfLqYS31KFN0s3qlaD9qCaxACA==} dev: true /@types/parse-json/4.0.0: @@ -2799,7 +2709,7 @@ packages: /@types/resolve/1.17.1: resolution: {integrity: sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==} dependencies: - '@types/node': 16.4.0 + '@types/node': 18.0.0 dev: true /@types/retry/0.12.0: @@ -2820,13 +2730,13 @@ packages: resolution: {integrity: sha512-nCkHGI4w7ZgAdNkrEu0bv+4xNV/XDqW+DydknebMOQwkpDGx8G+HTlj7R7ABI8i8nKxVw0wtKPi1D+lPOkh4YQ==} dependencies: '@types/mime': 1.3.2 - '@types/node': 16.4.0 + '@types/node': 18.0.0 dev: true /@types/sockjs/0.3.33: resolution: {integrity: sha512-f0KEEe05NvUnat+boPTZ0dgaLZ4SfSouXUgv5noUiefG2ajgKjmETo9ZJyuqsl7dfl2aHlLJUiki6B4ZYldiiw==} dependencies: - '@types/node': 16.4.0 + '@types/node': 18.0.0 dev: true /@types/stack-utils/2.0.1: @@ -2840,7 +2750,7 @@ packages: /@types/ws/8.5.3: resolution: {integrity: sha512-6YOoWjruKj1uLf3INHH7D3qTXwFfEsg1kf3c0uDdSBJwfa/llkwIjrAGV7j7mVgGNbzTQ3HiHKKDXl6bJPD97w==} dependencies: - '@types/node': 16.4.0 + '@types/node': 18.0.0 dev: true /@types/yargs-parser/20.2.1: @@ -2998,20 +2908,6 @@ packages: eslint-visitor-keys: 3.3.0 dev: true - /@vitejs/plugin-react-refresh/1.3.6: - resolution: {integrity: sha512-iNR/UqhUOmFFxiezt0em9CgmiJBdWR+5jGxB2FihaoJfqGt76kiwaKoVOJVU5NYcDWMdN06LbyN2VIGIoYdsEA==} - engines: {node: '>=12.0.0'} - deprecated: This package has been deprecated in favor of @vitejs/plugin-react - dependencies: - '@babel/core': 7.14.8 - '@babel/plugin-transform-react-jsx-self': 7.14.5_@babel+core@7.14.8 - '@babel/plugin-transform-react-jsx-source': 7.14.5_@babel+core@7.14.8 - '@rollup/pluginutils': 4.1.1 - react-refresh: 0.10.0 - transitivePeerDependencies: - - supports-color - dev: true - /@vitejs/plugin-react/1.3.2: resolution: {integrity: sha512-aurBNmMo0kz1O4qRoY+FM4epSA39y3ShWGuqfLRA/3z0oEJAdtoSfgA3aO98/PCCHAqMaduLxIxErWrVKIFzXA==} engines: {node: '>=12.0.0'} @@ -6374,7 +6270,7 @@ packages: '@jest/environment': 27.5.1 '@jest/test-result': 27.5.1 '@jest/types': 27.5.1 - '@types/node': 16.4.0 + '@types/node': 18.0.0 chalk: 4.1.2 co: 4.6.0 dedent: 0.7.0 @@ -6499,7 +6395,7 @@ packages: '@jest/environment': 27.5.1 '@jest/fake-timers': 27.5.1 '@jest/types': 27.5.1 - '@types/node': 16.4.0 + '@types/node': 18.0.0 jest-mock: 27.5.1 jest-util: 27.5.1 jsdom: 16.6.0 @@ -6517,7 +6413,7 @@ packages: '@jest/environment': 27.5.1 '@jest/fake-timers': 27.5.1 '@jest/types': 27.5.1 - '@types/node': 16.4.0 + '@types/node': 18.0.0 jest-mock: 27.5.1 jest-util: 27.5.1 dev: true @@ -6533,7 +6429,7 @@ packages: dependencies: '@jest/types': 27.5.1 '@types/graceful-fs': 4.1.5 - '@types/node': 16.4.0 + '@types/node': 18.0.0 anymatch: 3.1.2 fb-watchman: 2.0.1 graceful-fs: 4.2.10 @@ -6555,7 +6451,7 @@ packages: '@jest/source-map': 27.5.1 '@jest/test-result': 27.5.1 '@jest/types': 27.5.1 - '@types/node': 16.4.0 + '@types/node': 18.0.0 chalk: 4.1.2 co: 4.6.0 expect: 27.5.1 @@ -6625,7 +6521,7 @@ packages: engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} dependencies: '@jest/types': 27.5.1 - '@types/node': 16.4.0 + '@types/node': 18.0.0 dev: true /jest-pnp-resolver/1.2.2_jest-resolve@27.5.1: @@ -6686,7 +6582,7 @@ packages: '@jest/test-result': 27.5.1 '@jest/transform': 27.5.1 '@jest/types': 27.5.1 - '@types/node': 16.4.0 + '@types/node': 18.0.0 chalk: 4.1.2 emittery: 0.8.1 graceful-fs: 4.2.10 @@ -6743,7 +6639,7 @@ packages: resolution: {integrity: sha512-jZCyo6iIxO1aqUxpuBlwTDMkzOAJS4a3eYz3YzgxxVQFwLeSA7Jfq5cbqCY+JLvTDrWirgusI/0KwxKMgrdf7w==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} dependencies: - '@types/node': 16.4.0 + '@types/node': 18.0.0 graceful-fs: 4.2.10 dev: true @@ -6782,7 +6678,7 @@ packages: engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} dependencies: '@jest/types': 27.5.1 - '@types/node': 16.4.0 + '@types/node': 18.0.0 chalk: 4.1.2 ci-info: 3.3.2 graceful-fs: 4.2.10 @@ -6794,7 +6690,7 @@ packages: engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} dependencies: '@jest/types': 28.1.1 - '@types/node': 16.4.0 + '@types/node': 18.0.0 chalk: 4.1.2 ci-info: 3.3.2 graceful-fs: 4.2.10 @@ -6835,7 +6731,7 @@ packages: dependencies: '@jest/test-result': 27.5.1 '@jest/types': 27.5.1 - '@types/node': 16.4.0 + '@types/node': 18.0.0 ansi-escapes: 4.3.2 chalk: 4.1.2 jest-util: 27.5.1 @@ -6848,7 +6744,7 @@ packages: dependencies: '@jest/test-result': 28.1.1 '@jest/types': 28.1.1 - '@types/node': 16.4.0 + '@types/node': 18.0.0 ansi-escapes: 4.3.2 chalk: 4.1.2 emittery: 0.10.2 @@ -6860,7 +6756,7 @@ packages: resolution: {integrity: sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==} engines: {node: '>= 10.13.0'} dependencies: - '@types/node': 16.4.0 + '@types/node': 18.0.0 merge-stream: 2.0.0 supports-color: 7.2.0 dev: true @@ -6869,7 +6765,7 @@ packages: resolution: {integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==} engines: {node: '>= 10.13.0'} dependencies: - '@types/node': 16.4.0 + '@types/node': 18.0.0 merge-stream: 2.0.0 supports-color: 8.1.1 dev: true @@ -6878,7 +6774,7 @@ packages: resolution: {integrity: sha512-Au7slXB08C6h+xbJPp7VIb6U0XX5Kc9uel/WFc6/rcTzGiaVCBRngBExSYuXSLFPULPSYU3cJ3ybS988lNFQhQ==} engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} dependencies: - '@types/node': 16.4.0 + '@types/node': 18.0.0 merge-stream: 2.0.0 supports-color: 8.1.1 dev: true @@ -7001,14 +6897,6 @@ packages: minimist: 1.2.6 dev: true - /json5/2.2.0: - resolution: {integrity: sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==} - engines: {node: '>=6'} - hasBin: true - dependencies: - minimist: 1.2.5 - dev: true - /json5/2.2.1: resolution: {integrity: sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==} engines: {node: '>=6'} @@ -7209,6 +7097,12 @@ packages: tmpl: 1.0.4 dev: true + /marked/4.0.17: + resolution: {integrity: sha512-Wfk0ATOK5iPxM4ptrORkFemqroz0ZDxp5MWfYA7H/F+wO17NRWV5Ypxi6p3g2Xmw2bKeiYOl6oVnLHKxBA0VhA==} + engines: {node: '>= 12'} + hasBin: true + dev: false + /mdn-data/2.0.14: resolution: {integrity: sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==} dev: true @@ -7323,10 +7217,6 @@ packages: brace-expansion: 2.0.1 dev: true - /minimist/1.2.5: - resolution: {integrity: sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==} - dev: true - /minimist/1.2.6: resolution: {integrity: sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==} dev: true @@ -7722,11 +7612,6 @@ packages: resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} dev: true - /picomatch/2.3.0: - resolution: {integrity: sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==} - engines: {node: '>=8.6'} - dev: true - /picomatch/2.3.1: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} @@ -8742,11 +8627,6 @@ packages: resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==} dev: true - /react-refresh/0.10.0: - resolution: {integrity: sha512-PgidR3wST3dDYKr6b4pJoqQFpPGNKDSCDx4cZoshjXipw3LzO7mG1My2pwEzz2JVkF+inx3xRpDeQLFQGH/hsQ==} - engines: {node: '>=0.10.0'} - dev: true - /react-refresh/0.11.0: resolution: {integrity: sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A==} engines: {node: '>=0.10.0'} @@ -9406,6 +9286,7 @@ packages: /source-map/0.5.7: resolution: {integrity: sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=} engines: {node: '>=0.10.0'} + dev: false /source-map/0.6.1: resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} diff --git a/public/blog/2017/convergence-of-world-lines.md b/public/blog/2017/convergence-of-world-lines.md new file mode 100644 index 0000000..b3b3623 --- /dev/null +++ b/public/blog/2017/convergence-of-world-lines.md @@ -0,0 +1,33 @@ +--- +title: Convergence of World Lines +published: December 14, 2017 +category: Thoughts +featured-img: convergence.png +snippet: In an infinite number of universes where anything is possible, I want to meet you again in all of them, and fall in love with you all over again. +--- + +I’ve been watching Steins;Gate over the last week and I’ve been completely blown away by the sheer amount of work gone into this series. When it ended. it left me speechless and sad because I missed being part of the universe of Okabe Rintarou (Hououin Kyouma) and his lab. I learnt that a sequel is in the making and the visual novel Steins;Gate 0 is already out… But in the mean time, I needed to let some feelings out. + +> No matter how many times I have to watch [death] to save you, it’s not going to break me! +> +> – Okabe Rintarou + +Okabe repeatedly travels to the past, each time to watch someone close to him die, and fervently keeps trying despite the mental trauma he experiences. This line affected me the hardest, because it reminded me of something I told someone, a long time ago. + +> In an infinite number of universes where anything is possible, I want to meet you again in all of them, and fall in love with you all over again. + +And again, when recollecting to someone else: + +> If I knew what was going to happen, and how we would separate, I wouldn’t change a thing about it. I would go back in time and watch us fall in love, and watch her break apart, without a moment of regret. + +It’s funny how world lines converge to form a consistent story-line, some times even from a fictional world. We relate to fictional characters, a piece of someone else’s imagination, and we live them with our lives… Well then, who is to say they aren’t real? + +I realize it’s December 14th, 4:00 AM as I write this. today, 3 years ago, the event that will separate us will happen. After all this time, I haven’t changed. I hoped I would, and I tried. I thought I moved on, but I am emotionally and mentally bound to the past. I do not think that I want us back together. If she is happy elsewhere, then that’s the reality I want to see. I’m simply in love with a woman in the past. It’s sad, but it’s what makes me me. All the events that happened in the past 22 years are part of the threads that form the continuum that is me. + +![Threads of time](/blog/assets/threads-of-time.jpg) + +Okabe reached the Steins;Gate world line… Maybe this is my Steins;Gate world line, and this is the best outcome. Maybe our souls passed by and are fated to never meet again— + +![kurisu-okabe-crossing](/blog/assets/kurisu-okabe-crossing.jpg) + +🎶 Maybe this is for the best — diff --git a/public/blog/assets/convergence.png b/public/blog/assets/convergence.png new file mode 100644 index 0000000..d367777 Binary files /dev/null and b/public/blog/assets/convergence.png differ diff --git a/public/blog/assets/kurisu-okabe-crossing.jpg b/public/blog/assets/kurisu-okabe-crossing.jpg new file mode 100644 index 0000000..139389e Binary files /dev/null and b/public/blog/assets/kurisu-okabe-crossing.jpg differ diff --git a/public/blog/assets/threads-of-time.jpg b/public/blog/assets/threads-of-time.jpg new file mode 100644 index 0000000..6428dc7 Binary files /dev/null and b/public/blog/assets/threads-of-time.jpg differ diff --git a/scripts/blog.js b/scripts/blog.js new file mode 100644 index 0000000..a9f9a41 --- /dev/null +++ b/scripts/blog.js @@ -0,0 +1,50 @@ +import { readdir, readFile, writeFile } from "node:fs/promises"; + +const toplevel = (await readdir("blog")).filter(x => x !== "assets"); + +const removeExtn = path => { + const parts = path.split("."); + parts.splice(parts.length - 1); + return parts.join("."); +}; + +const parseMetadata = (slug, contents) => { + return Object.fromEntries( + contents + .split("---")[1] + .trim() + .split("\n") + .map(line => { + const [name, value] = line.split(/:(.*)/).map(x => x.trim()); + return [name, value]; + }) + .concat([["slug", slug]]), + ); +}; + +const data = Object.fromEntries( + await Promise.all( + toplevel.map(year => + readdir("public/blog/" + year).then(slugs => + Promise.all( + slugs.map(async slug => { + const path = `public/blog/${year}/${slug}`; + const contents = await readFile(path, "utf-8"); + return parseMetadata(removeExtn(slug), contents); + }), + ) + .then(list => + list + .sort((a, b) => new Date(a).valueOf() - new Date(b).valueOf()) + .map(x => [x.slug, x]), + ) + .then(list => Object.fromEntries(list)) + .then(cont => [year, cont]), + ), + ), + ), +); + +writeFile("src/blog.json", JSON.stringify(data, null, "\t"), "utf-8").then(() => + console.log("Done"), +); diff --git a/src/assets/arrow-thin.svg b/src/assets/arrow-thin.svg new file mode 100644 index 0000000..65f3f84 --- /dev/null +++ b/src/assets/arrow-thin.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/blog.css b/src/blog.css new file mode 100644 index 0000000..e07c03e --- /dev/null +++ b/src/blog.css @@ -0,0 +1,17 @@ +:root { + --primary-colour: #d6a700; + font-size: max(18px, 0.8vw); +} + +h1, +h2, +h3, +h4, +h5, +h6 { + color: var(--text-colour); +} + +a:hover { + color: var(--text-colour); +} diff --git a/src/blog.json b/src/blog.json new file mode 100644 index 0000000..b380411 --- /dev/null +++ b/src/blog.json @@ -0,0 +1,32 @@ +{ + "2016": { + "convergence-of-world-lines-2": { + "title": "Convergence of World Lines 2", + "published": "December 14, 2016", + "category": "Thoughts", + "featured-img": "convergence.png", + "snippet": "In an infinite number of universes where anything is possible, I want to meet you again in all of them, and fall in love with you all over again.", + "slug": "convergence-of-world-lines-2" + } + }, + "2017": { + "convergence-of-world-lines": { + "title": "Convergence of World Lines", + "published": "December 14, 2017", + "category": "Thoughts", + "featured-img": "convergence.png", + "snippet": "In an infinite number of universes where anything is possible, I want to meet you again in all of them, and fall in love with you all over again.", + "slug": "convergence-of-world-lines" + } + }, + "2018": { + "convergence-of-world-lines-3": { + "title": "Convergence of World Lines 3", + "published": "December 14, 2018", + "category": "Thoughts", + "featured-img": "convergence.png", + "snippet": "In an infinite number of universes where anything is possible, I want to meet you again in all of them, and fall in love with you all over again.", + "slug": "convergence-of-world-lines-3" + } + } +} \ No newline at end of file diff --git a/src/blog.tsx b/src/blog.tsx new file mode 100644 index 0000000..6899610 --- /dev/null +++ b/src/blog.tsx @@ -0,0 +1,15 @@ +import React from "react"; + +import { createRoot } from "react-dom/client"; +import { BrowserRouter as Router } from "react-router-dom"; +import "./index.css"; + +import BlogHome from "./pages/blog/Home"; + +createRoot(document.getElementById("root")!).render( + + + + + , +); diff --git a/src/components/Menu.tsx b/src/components/Menu.tsx index 6ff3b59..7e0eb19 100644 --- a/src/components/Menu.tsx +++ b/src/components/Menu.tsx @@ -43,10 +43,6 @@ const menuList = css` & > li { margin-left: 1rem; - - & > a { - text-decoration: none; - } } `; diff --git a/src/components/Spacer.tsx b/src/components/Spacer.tsx new file mode 100644 index 0000000..3ab26b4 --- /dev/null +++ b/src/components/Spacer.tsx @@ -0,0 +1,23 @@ +import React from "react"; + +type Props = { + inline?: boolean; + x?: number; + y?: number; +}; + +const rem = (x?: number) => (x === 0 ? x : x ? `${x}rem` : "100%"); + +export const Spacer: React.FC = ({ inline, x, y = 1 }) => { + return ( + + ); +}; diff --git a/src/data.ts b/src/data.ts new file mode 100644 index 0000000..0ca6805 --- /dev/null +++ b/src/data.ts @@ -0,0 +1,34 @@ +import json from "./blog.json"; + +export type Article = { + "title": string; + "category": string; + "snippet": string; + "slug": string; + "published": string; + "featured-img": string; +}; + +export const blog = json as Record>; + +export const articles = Object.values(blog) + .flatMap(year => Object.values(year)) + .sort( + (a, b) => new Date(b.published).valueOf() - new Date(a.published).valueOf(), + ); + +export const nextAndPrev = ( + year: string, + slug: string, +): [Article | undefined, Article | undefined] => { + const idx = articles.findIndex( + article => + String(new Date(article.published).getFullYear()) === year && + article.slug === slug, + ); + + return [articles[idx - 1], articles[idx + 1]]; +}; + +export const getBlogPath = (article: Article) => + `/blog/${new Date(article.published).getFullYear()}/${article.slug}`; diff --git a/src/index.css b/src/index.css index 205c75b..5021db0 100644 --- a/src/index.css +++ b/src/index.css @@ -5,6 +5,7 @@ --card-tags-hover: rgb(25, 25, 25); --primary-colour: rgb(255, 85, 85); --text-colour: rgb(211, 207, 201); + --text-subdued: rgb(163, 163, 163); font-weight: 500; font-size: max(16px, 0.8vw); } @@ -19,6 +20,11 @@ body { font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; color: var(--text-colour); + height: 100vh; +} + +#root { + height: 100%; } code { @@ -68,6 +74,7 @@ h4 { a { color: var(--text-colour); + text-decoration: none; } a:hover { diff --git a/src/index.tsx b/src/index.tsx index 359e65a..8d1708d 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -12,6 +12,7 @@ import Contact from "./pages/main/Contact"; import Live from "./pages/main/Live"; import NotFound from "./pages/main/404"; +import BlogHome from "./pages/blog/Home"; createRoot(document.getElementById("root")!).render( @@ -23,6 +24,8 @@ createRoot(document.getElementById("root")!).render( } /> } /> + } /> + } /> diff --git a/src/pages/blog/Home.tsx b/src/pages/blog/Home.tsx new file mode 100644 index 0000000..e97d54d --- /dev/null +++ b/src/pages/blog/Home.tsx @@ -0,0 +1,142 @@ +import { css } from "@emotion/css"; +import React from "react"; +import { Routes, Route, Link } from "react-router-dom"; +import { Spacer } from "../../components/Spacer"; +import { ArticleSubHeader } from "./components/ArticleSubHeader"; +import { BlogPost } from "./components/BlogContent"; +import { articles, getBlogPath } from "../../data"; + +const Header: React.FC = () => { + return ( +
+
+

+ #MKR +

+

+ Words from{" "} + + Muthu Kumar + +

+
+
+

Designer / Developer / Architect

+
+
+ ); +}; + +const BlogHome: React.FC = () => { + return ( +
+ +
+ +
} /> + } /> + + + + ); +}; + +export default BlogHome; diff --git a/src/pages/blog/components/ArticleSubHeader.tsx b/src/pages/blog/components/ArticleSubHeader.tsx new file mode 100644 index 0000000..f8ae108 --- /dev/null +++ b/src/pages/blog/components/ArticleSubHeader.tsx @@ -0,0 +1,21 @@ +import { css } from "@emotion/css"; +import React from "react"; +import { Article } from "../../../data"; + +export const ArticleSubHeader: React.FC<{ article: Article }> = ({ + article: { category, published }, +}) => { + return ( +
+ {category} + · + {new Date(published).toLocaleDateString()} +
+ ); +}; diff --git a/src/pages/blog/components/BlogContent.tsx b/src/pages/blog/components/BlogContent.tsx new file mode 100644 index 0000000..f6dbe47 --- /dev/null +++ b/src/pages/blog/components/BlogContent.tsx @@ -0,0 +1,210 @@ +import React, { useEffect, useState } from "react"; +import { useLocation } from "react-router-dom"; +import { marked } from "marked"; +import { Article, blog, getBlogPath, nextAndPrev } from "../../../data"; +import "../../../blog.css"; +import { ArticleSubHeader } from "./ArticleSubHeader"; +import { css, cx } from "@emotion/css"; +import { ReactComponent as Arrow } from "../../../assets/arrow-thin.svg"; +import { ellipses, useNav } from "../../../util"; + +const Markdown: React.FC<{ content: string }> = ({ content }) => { + return ( +
+ ); +}; + +const Preview: React.FC<{ article: Article }> = ({ article }) => { + return ( +
+ Featured +
+

{article.title}

+

{ellipses(article.snippet, 110)}

+
+
+ ); +}; + +const btn = css` + background-color: #535353; + border: 0; + cursor: pointer; + border-radius: 0.5rem; + padding: 0.5rem 1.4rem; + color: #c8c8c8; + font-size: 1.2rem; + display: flex; + align-items: center; + gap: 0.8rem; + font-weight: 600; + transition: background-color 150ms; + + &:hover { + background-color: #414141; + color: var(--text-colour); + } + + & svg { + height: 2rem; + width: 1.5rem; + } +`; + +export const BlogPost: React.FC = () => { + const navigate = useNav(); + const location = useLocation(); + const [content, setContent] = useState(""); + const [error, setError] = useState(""); + const [loading, setLoading] = useState(true); + + const [year, slug] = location.pathname.split("/").slice(-2); + const article = blog[year]?.[slug]; + + const [next, prev] = nextAndPrev(year, slug); + + useEffect(() => { + async function query() { + setLoading(true); + + const path = getBlogPath(article) + ".md"; + const res = await fetch(path); + console.log(res.status); + // not success and not a cached response + if (res.status > 299 && res.status !== 304) { + if (res.status > 399) { + const err = await res.text().catch(() => "Unknown error"); + return setError(err); + } else return setError("Unexpected redirect"); + } + + const content = await res.text(); + + setContent(content.split("---").slice(2).join("---")); + setLoading(false); + } + + query(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [location]); + + if (loading) return
Loading...
; + + if (!article || error) return
{error || "Unknown error occurred"}
; + + return ( + <> + + Featured +

+ {article.title} +

+ + +
+ {prev && ( + + + + + )} + {next && ( + + + + + )} +
+ + ); +}; diff --git a/src/util/index.ts b/src/util/index.ts index 3988943..2bb4df1 100644 --- a/src/util/index.ts +++ b/src/util/index.ts @@ -1,3 +1,6 @@ +import React from "react"; +import { useNavigate } from "react-router-dom"; + export const getTimeout = () => { const clearables = new Set(); @@ -13,3 +16,15 @@ export const getTimeout = () => { return [timeout, clearTimers] as const; }; + +export const ellipses = (text: string, length: number = 100) => + text.length > length ? text.slice(0, length - 3) + "..." : text; + +export const useNav = () => { + const navigate = useNavigate(); + + return (link: string) => (e: React.MouseEvent) => { + if (e.ctrlKey) return window.open(link, "_blank", "noreferrer noopener"); + navigate(link); + }; +}; diff --git a/vite.config.ts b/vite.config.ts index 5f82ea8..db4921d 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,11 +1,18 @@ import { defineConfig } from "vite"; +import { resolve } from "path"; import react from "@vitejs/plugin-react"; import svgr from "@svgr/rollup"; // https://vitejs.dev/config/ export default defineConfig({ - server: { - port: 12000, - }, + server: { port: 3000 }, plugins: [react(), Object.assign(svgr({ ref: true, svgo: false }), { enforce: "pre" } as const)], + build: { + rollupOptions: { + input: { + main: resolve(__dirname, "index.html"), + blog: resolve(__dirname, "blog.html"), + }, + }, + }, });