diff --git a/mdsvex.config.js b/mdsvex.config.js new file mode 100644 index 0000000..5576a98 --- /dev/null +++ b/mdsvex.config.js @@ -0,0 +1,3 @@ +export default { + extensions: ['.md'] +}; diff --git a/package-lock.json b/package-lock.json index 44d084e..1ec73c8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,7 +21,7 @@ "eslint": "^9.21.0", "eslint-config-prettier": "^10.0.1", "eslint-plugin-svelte": "^3.0.0", - "mdsvex": "^0.12.6", + "mdsvex": "^0.12.3", "mdsvex-relative-images": "^1.0.3", "postcss": "^8.5.3", "prettier": "^3.5.2", @@ -521,9 +521,9 @@ } }, "node_modules/@eslint/config-array": { - "version": "0.21.0", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz", - "integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==", + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.19.2.tgz", + "integrity": "sha512-GNKqxfHG2ySmJOBSHg7LxeUx4xpuCoFjacmlCoYWEbaPXLwvfIjixRI12xCQZeULksQb23uiA8F40w5TojpV7w==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -535,20 +535,10 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@eslint/config-helpers": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.0.tgz", - "integrity": "sha512-ViuymvFmcJi04qdZeDc2whTHryouGcDlaxPqarTD0ZE10ISpxGUVZGZDx4w01upyIynL3iu6IXH2bS1NhclQMw==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, "node_modules/@eslint/core": { - "version": "0.15.2", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.2.tgz", - "integrity": "sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==", + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.12.0.tgz", + "integrity": "sha512-cmrR6pytBuSMTaBweKoGMwu3EiHiEC+DoyupPmlZ0HxBJBtIxwe+j/E4XPIKNx+Q74c8lXKPwYawBf5glsTkHg==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -559,9 +549,9 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", - "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.0.tgz", + "integrity": "sha512-yaVPAiNAalnCZedKLdR21GOGILMLKPyqSLWaAjQFvYA2i/ciDi8ArYVr69Anohb6cH2Ukhqti4aFnYyPm8wdwQ==", "dev": true, "license": "MIT", "dependencies": { @@ -583,16 +573,13 @@ } }, "node_modules/@eslint/js": { - "version": "9.31.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.31.0.tgz", - "integrity": "sha512-LOm5OVt7D4qiKCqoiPbA7LWmI+tbw1VbTUowBcUMgQSuM6poJufkFkYDcQpo5KfgD39TnNySV26QjOh7VFpSyw==", + "version": "9.21.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.21.0.tgz", + "integrity": "sha512-BqStZ3HX8Yz6LvsF5ByXYrtigrV5AXADWLAGc7PH/1SxOb7/FIYYMszZZWiUou/GB9P2lXWk2SV4d+Z8h0nknw==", "dev": true, "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://eslint.org/donate" } }, "node_modules/@eslint/object-schema": { @@ -606,13 +593,13 @@ } }, "node_modules/@eslint/plugin-kit": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.5.tgz", - "integrity": "sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==", + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.7.tgz", + "integrity": "sha512-JubJ5B2pJ4k4yGxaNLdbjrnk9d/iDz6/q8wOilpIowd6PJPgaxCuHBnBszq7Ce2TyMrywm5r4PnKm6V3iiZF+g==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^0.15.2", + "@eslint/core": "^0.12.0", "levn": "^0.4.1" }, "engines": { @@ -1172,13 +1159,6 @@ "win32" ] }, - "node_modules/@standard-schema/spec": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz", - "integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==", - "dev": true, - "license": "MIT" - }, "node_modules/@svelte-put/dragscroll": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@svelte-put/dragscroll/-/dragscroll-4.0.0.tgz", @@ -1213,9 +1193,9 @@ } }, "node_modules/@sveltejs/adapter-static": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/@sveltejs/adapter-static/-/adapter-static-3.0.9.tgz", - "integrity": "sha512-aytHXcMi7lb9ljsWUzXYQ0p5X1z9oWud2olu/EpmH7aCu4m84h7QLvb5Wp+CFirKcwoNnYvYWhyP/L8Vh1ztdw==", + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@sveltejs/adapter-static/-/adapter-static-3.0.8.tgz", + "integrity": "sha512-YaDrquRpZwfcXbnlDsSrBQNCChVOT9MGuSg+dMAyfsAa1SmiAhrA5jUYUiIMC59G92kIbY/AaQOWcBdq+lh+zg==", "dev": true, "license": "MIT", "peerDependencies": { @@ -1223,26 +1203,25 @@ } }, "node_modules/@sveltejs/kit": { - "version": "2.43.5", - "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.43.5.tgz", - "integrity": "sha512-44Mm5csR4mesKx2Eyhtk8UVrLJ4c04BT2wMTfYGKJMOkUqpHP5KLL2DPV0hXUA4t4+T3ZYe0aBygd42lVYv2cA==", + "version": "2.22.2", + "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.22.2.tgz", + "integrity": "sha512-2MvEpSYabUrsJAoq5qCOBGAlkICjfjunrnLcx3YAk2XV7TvAIhomlKsAgR4H/4uns5rAfYmj7Wet5KRtc8dPIg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@standard-schema/spec": "^1.0.0", "@sveltejs/acorn-typescript": "^1.0.5", "@types/cookie": "^0.6.0", "acorn": "^8.14.1", "cookie": "^0.6.0", - "devalue": "^5.3.2", + "devalue": "^5.1.0", "esm-env": "^1.2.2", "kleur": "^4.1.5", "magic-string": "^0.30.5", "mrmime": "^2.0.0", "sade": "^1.8.1", "set-cookie-parser": "^2.6.0", - "sirv": "^3.0.0" + "sirv": "^3.0.0", + "vitefu": "^1.0.6" }, "bin": { "svelte-kit": "svelte-kit.js" @@ -1251,15 +1230,9 @@ "node": ">=18.13" }, "peerDependencies": { - "@opentelemetry/api": "^1.0.0", "@sveltejs/vite-plugin-svelte": "^3.0.0 || ^4.0.0-next.1 || ^5.0.0 || ^6.0.0-next.0", "svelte": "^4.0.0 || ^5.0.0-next.0", "vite": "^5.0.3 || ^6.0.0 || ^7.0.0-beta.0" - }, - "peerDependenciesMeta": { - "@opentelemetry/api": { - "optional": true - } } }, "node_modules/@sveltejs/vite-plugin-svelte": { @@ -1291,6 +1264,7 @@ "integrity": "sha512-J/Nmb2Q2y7mck2hyCX4ckVHcR5tu2J+MtBEQqpDrrgELZ2uvraQcK/ioCV61AqkdXFgriksOKIceDcQmqnGhVw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "debug": "^4.3.7" }, @@ -1578,23 +1552,12 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/mdast": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", - "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/unist": "*" - } - }, "node_modules/@types/node": { "version": "22.13.5", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.5.tgz", "integrity": "sha512-+lTU0PxZXn0Dr1NBtC7Y8cR21AJr87dLLU953CWA6pMxxv/UDc7jYAY90upcrie1nRcD6XNG5HOYEDtgW5TxAg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~6.20.0" } @@ -1612,7 +1575,6 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -1789,7 +1751,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001688", "electron-to-chromium": "^1.5.73", @@ -1973,6 +1934,7 @@ "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -1991,9 +1953,9 @@ } }, "node_modules/devalue": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.3.2.tgz", - "integrity": "sha512-UDsjUbpQn9kvm68slnrs+mfxwFkIflOhkanmyabZ8zOYk8SMEIbJ3TK+88g70hSIeytu4y18f0z/hYHMTrXIWw==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.1.1.tgz", + "integrity": "sha512-maua5KUiapvEwiEAe+XnlZ3Rh0GD+qI1J/nb9vrJc3muPXvcF/8gXYTWF76+5DAqHyDUtOIImEuo0YKE9mshVw==", "dev": true, "license": "MIT" }, @@ -2083,21 +2045,19 @@ } }, "node_modules/eslint": { - "version": "9.31.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.31.0.tgz", - "integrity": "sha512-QldCVh/ztyKJJZLr4jXNUByx3gR+TDYZCRXEktiZoUR3PGy4qCmSbkxcIle8GEwGpb5JBZazlaJ/CxLidXdEbQ==", + "version": "9.21.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.21.0.tgz", + "integrity": "sha512-KjeihdFqTPhOMXTt7StsDxriV4n66ueuF/jfPNC3j/lduHwr/ijDwJMsF+wyMJethgiKi5wniIE243vi07d3pg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.21.0", - "@eslint/config-helpers": "^0.3.0", - "@eslint/core": "^0.15.0", - "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.31.0", - "@eslint/plugin-kit": "^0.3.1", + "@eslint/config-array": "^0.19.2", + "@eslint/core": "^0.12.0", + "@eslint/eslintrc": "^3.3.0", + "@eslint/js": "9.21.0", + "@eslint/plugin-kit": "^0.2.7", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", @@ -2108,9 +2068,9 @@ "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.4.0", - "eslint-visitor-keys": "^4.2.1", - "espree": "^10.4.0", + "eslint-scope": "^8.2.0", + "eslint-visitor-keys": "^4.2.0", + "espree": "^10.3.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", @@ -2208,9 +2168,9 @@ } }, "node_modules/eslint-scope": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", - "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.2.0.tgz", + "integrity": "sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -2225,9 +2185,9 @@ } }, "node_modules/eslint-visitor-keys": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", "dev": true, "license": "Apache-2.0", "engines": { @@ -2245,15 +2205,15 @@ "license": "MIT" }, "node_modules/espree": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", - "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", + "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "acorn": "^8.15.0", + "acorn": "^8.14.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.2.1" + "eslint-visitor-keys": "^4.2.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3045,17 +3005,15 @@ } }, "node_modules/mdsvex": { - "version": "0.12.6", - "resolved": "https://registry.npmjs.org/mdsvex/-/mdsvex-0.12.6.tgz", - "integrity": "sha512-pupx2gzWh3hDtm/iDW4WuCpljmyHbHi34r7ktOqpPGvyiM4MyfNgdJ3qMizXdgCErmvYC9Nn/qyjePy+4ss9Wg==", + "version": "0.12.3", + "resolved": "https://registry.npmjs.org/mdsvex/-/mdsvex-0.12.3.tgz", + "integrity": "sha512-C/uIJamjNo5PHHnR3JHqsBPoLcfUBpzRmAEB6FLMXI/s7XHOceswjDMKqSPEW2WHmYpKm0taZ3U20GSyhMridA==", "dev": true, "license": "MIT", "dependencies": { - "@types/mdast": "^4.0.4", "@types/unist": "^2.0.3", "prism-svelte": "^0.4.7", "prismjs": "^1.17.1", - "unist-util-visit": "^2.0.1", "vfile-message": "^2.0.4" }, "peerDependencies": { @@ -3073,48 +3031,6 @@ "unist-util-visit": "^3.1.0" } }, - "node_modules/mdsvex/node_modules/unist-util-is": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-4.1.0.tgz", - "integrity": "sha512-ZOQSsnce92GrxSqlnEEseX0gi7GH9zTJZ0p9dtu87WRb/37mMPO2Ilx1s/t9vBHrFhbgweUwb+t7cIn5dxPhZg==", - "dev": true, - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdsvex/node_modules/unist-util-visit": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-2.0.3.tgz", - "integrity": "sha512-iJ4/RczbJMkD0712mGktuGpm/U4By4FfDonL7N/9tATGIF4imikjOuagyMY53tnZq3NP6BcmlrHhEKAfGWjh7Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/unist": "^2.0.0", - "unist-util-is": "^4.0.0", - "unist-util-visit-parents": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdsvex/node_modules/unist-util-visit-parents": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-3.1.1.tgz", - "integrity": "sha512-1KROIZWo6bcMrZEwiH2UrXDyalAa0uqzWCxCJj6lPOvTve2WkfgCytoDTPaMnodXh1WrXOq0haVYHj99ynJlsg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/unist": "^2.0.0", - "unist-util-is": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -3341,7 +3257,6 @@ "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -3369,7 +3284,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "nanoid": "^3.3.8", "picocolors": "^1.1.1", @@ -3510,7 +3424,6 @@ "integrity": "sha512-lc6npv5PH7hVqozBR7lkBNOGXV9vMwROAPlumdBkX0wTbbzPu/U1hk5yL8p2pt4Xoc+2mkT8t/sow2YrV/M5qg==", "dev": true, "license": "MIT", - "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -3527,7 +3440,6 @@ "integrity": "sha512-yViK9zqQ+H2qZD1w/bH7W8i+bVfKrD8GIFjkFe4Thl6kCT9SlAsXVNmt3jCvQOCsnOhcvYgsoVlRV/Eu6x5nNw==", "dev": true, "license": "MIT", - "peer": true, "peerDependencies": { "prettier": "^3.0.0", "svelte": "^3.2.0 || ^4.0.0-next.0 || ^5.0.0-next.0" @@ -3887,7 +3799,6 @@ "integrity": "sha512-2Mo/AfObaw9zuD0u1JJ7sOVzRCGcpETEyDkLbtkcctWpCMCIyT0iz83xD8JT29SR7O4SgswuPRIDYReYF/607A==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@ampproject/remapping": "^2.3.0", "@jridgewell/sourcemap-codec": "^1.5.0", @@ -4040,8 +3951,7 @@ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.0.9.tgz", "integrity": "sha512-12laZu+fv1ONDRoNR9ipTOpUD7RN9essRVkX36sjxuRUInpN7hIiHN4lBd/SIFjbISvnXzp8h/hXzmU8SQQYhw==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/tapable": { "version": "2.2.1", @@ -4119,7 +4029,6 @@ "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -4258,12 +4167,11 @@ } }, "node_modules/vite": { - "version": "6.3.6", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.6.tgz", - "integrity": "sha512-0msEVHJEScQbhkbVTb/4iHZdJ6SXp/AvxL2sjwYQFfBqleHtnCqv1J3sa9zbWz/6kW1m9Tfzn92vW+kZ1WV6QA==", + "version": "6.3.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz", + "integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", @@ -4418,6 +4326,21 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/yaml": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.0.tgz", + "integrity": "sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA==", + "dev": true, + "license": "ISC", + "optional": true, + "peer": true, + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index ee4f7bc..e206e05 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "eslint": "^9.21.0", "eslint-config-prettier": "^10.0.1", "eslint-plugin-svelte": "^3.0.0", - "mdsvex": "^0.12.6", + "mdsvex": "^0.12.3", "mdsvex-relative-images": "^1.0.3", "postcss": "^8.5.3", "prettier": "^3.5.2", diff --git a/src/app.css b/src/app.css index b80ef25..06dd7c0 100644 --- a/src/app.css +++ b/src/app.css @@ -26,15 +26,11 @@ src: url('/fonts/Disket-Mono-Bold.ttf'); } -.hero { - @apply bg-[url('/common/background-day.webp')] dark:bg-[url('/common/background-night.webp')] bg-cover bg-fixed text-slate-50; - text-shadow: 0 0 15px rgba(0, 0, 0, 0.25); +.no-x-scroll { + max-width: 100%; + overflow-x: hidden; +} - h1 { - @apply font-disket text-4xl font-bold sm:text-6xl md:text-8xl; - } - - h2 { - @apply font-sans text-xl font-bold sm:text-2xl md:text-4xl; - } +.hero-background { + @apply bg-[url('/common/background-day.webp')] dark:bg-[url('/common/background-night.webp')] bg-cover bg-fixed; } \ No newline at end of file diff --git a/src/app.d.ts b/src/app.d.ts index 9dfaddb..1243e75 100644 --- a/src/app.d.ts +++ b/src/app.d.ts @@ -1,3 +1,5 @@ +import type { Member } from '$lib/types/Member'; + declare global { namespace App { // interface Error {} @@ -18,25 +20,17 @@ declare global { type MdsvexResolver = () => Promise; - type BlogPostType = 'article' | 'update' | 'event'; - interface BlogPost { slug: string; - type?: BlogPostType; title: string; - thumbnail?: string; - thumbnailAlt?: string; - date?: string; - dateChanged?: string; - dateEventFrom?: string; - dateEventTo?: string; + thumbnail: string; + date: string; description: string; publisher: string; published?: boolean; - projects?: string[]; - authors?: string | string[]; + member?: Member; } } } -export { }; +export {}; diff --git a/src/app.html b/src/app.html index 862cd64..bd03bd5 100644 --- a/src/app.html +++ b/src/app.html @@ -5,7 +5,7 @@ - + diff --git a/src/blogs/hello_world.md b/src/blogs/hello_world.md deleted file mode 100644 index b00c051..0000000 --- a/src/blogs/hello_world.md +++ /dev/null @@ -1,166 +0,0 @@ ---- -title: 'Наш первый блог' -thumbnail: 'wave.png' -thumbnailAlt: 'Смайлик с махающей рукой' -date: '2025-10-22' -description: 'Немного о том, как мы делали наш сайт' -type: 'article' -authors: 'rndtrash' ---- - -# ПРИВЕТ ! - -Добро пожаловать на первый блог-пост нашего нового сайта! -На момент написания мы ведём работу над кучей проектов, и ввиду общей занятости [народа](/team), -работа идёт медленно, да и посты писать особо некому. - -Так давайте расскажем хотя бы про этот сайт! - -## Вводные данные - -Сайт написан на TypeScript с применением фреймворка [SvelteKit](https://svelte.dev). Некоторые отзываются о нём, мягко говоря, не лестно, -но нас Svelte подкупил тем, что все страницы, и даже ручки API, можно предварительно отрендерить по файлам, получив полностью статичный сайт! -А фреймворк [Tailwind CSS](https://tailwindcss.com) даёт нам удобные классы и неплохие дефолтные стили с цветовой палитрой. Пока у нас нет -_полноценных_ веб-дизайнеров, довольствуемся их встроенной палитрой. - -Это решение нам досталось в наследство от сайта [Small Fish](https://smallfi.sh). Впрочем, как и половина их кодовой базы: -для быстрого старта с разрешения команды мы воспользовались [их исходниками](https://github.com/Small-Fish-Dev/small-fish-dev.github.io). -Но вы не подумайте, что сайт слизан подчистую! Как минимум в процессе обновления версий фреймворков пришлось провести небольшой рефакторинг, -а потом ещё пошли наши прочие хотелки (об этом дальше), и можно смело сказать, что спустя -[сотню коммитов](https://git.teasanctuary.ru/TeaSanctuary/teasanctuary.ru/commits/branch/master), -из общего остались только фреймворки и сама структура сайта. - -В общем-то, главная причина почему мы взяли их сайт за основу, а не написали вручную — это удобная **система блогов**. - -## Собственно, блоги - -Конечно, в нашей небольшой команде абсолютно все компьютерно грамотные ([вы можете это исправить!](https://teasanctuary.ru/discord)), и никому не составит -труда настрочить текст вот так, просто HTML-ом. Но как же хочется писать блог в удобном WYSIWYG-редакторе, или хотя бы в простом формате! - -На помощь приходит библиотека [mdsvex](https://mdsvex.pngwn.io/). Она по сути превращает `.md` файлы в Svelte-компоненты, которые можно потом -встроить куда угодно. Здорово! - -Только надо решить парочку проблем. Во-первых, все элементы, вроде ссылок или картинок, переводятся в обычные HTML5-объекты. А у нас есть свои собственные, -особые ссылки ([вот такие!](https://хамяк.рф)). Благо, mdsvex позволяет просто использовать Svelte-компоненты, если правильно настроить авто-импортирование. -_Но это же не удобно!_ Это ведь надо знать, что есть _такие_ ссылки, а есть _не_ такие. Плюс, это будет выглядеть уродливо в тех же WYSIWYG-редакторах по -типу [Obsidian](https://obsidian.md). - -Решилось это с помощью системы layout-ов внутри mdsvex: можно задавать шаблоны, которые будут натянуты вокруг преобразованного Markdown-текста. Тут же -можно экспортировать особый объект, где к названию каждого стандартного HTML-элемента можно поставить ссылку на свой собственный класс. Таким образом, -каждый `...` превращается в `...`, без лишних телодвижений писателя блога. - -Отлично, у нас есть отдельные блоги, теперь хочется собрать их все вместе, и желательно рассортировать по дате. И вот тут всплывает вторая проблема: -система Git, которой мы пользуемся, просто не хранит дату создания и изменения файла! Надо это всё записать куда-то в метаданные. Самое простое решение: -присобачить к каждому Markdown-блогу по JSON-у. Но есть решение ещё лучше, оно -называется [frontmatter YAML](https://docs.github.com/ru/contributing/writing-for-github-docs/using-yaml-frontmatter) — де-факто стандарт встраивания -любых метаданных в файл Markdown. - -Здесь мы пишем дату в формате ISO, ну и на сдачу можно добавить кучу других данных. За примером далеко идти не надо: вот вам заголовок этой же заметки: - -```md ---- -title: 'Наш первый блог' -thumbnail: 'wave.png' -thumbnailAlt: 'Смайлик с махающей рукой' -date: '2025-10-22' -description: 'Немного о том, как мы делали наш сайт' -type: 'article' -authors: 'rndtrash' ---- -``` - -Тут вам и дата, и описание, и даже красивая миниатюра! И сюда можно вставить вообще всё, что поддерживает YAML, в том числе списки и булевы переменные. -Зачем? Ну-у-у... Потом разберёмся🙂 - -Важно то, что теперь с помощью `import.meta.glob('/src/blogs/*.md')` можно просто пройтись по всем постам, выдернуть метаданные и отсортировать их по дате. - -Эта система нам пригодилась в ещё одной вещи, которую, казалось бы, давно забыли, а зря! - -## RSS-лента - -Да, мы сами в шоке, но даже после становления фактической олигополии интернета Google-Twitter-ЧёЕщёТам, RSS-ленты продолжали использоваться для подкастов. -А с недавней волной популярности децентрализации ([Mastodon](https://joinmastodon.org/ru), [BlueSky](https://bsky.social), [NeoCities](https://neocities.org/) и прочие), -в моду вернулись маленькие бложики. _Да, мы знаем, что они никуда и не уходили, можете не писать гневные комментарии. Ах, да, их некуда писать. Ой._ - -RSS в этом плане нам очень удобен, во-первых, потому, что это просто XML-файл, и его можно просто куда-то положить, а во-вторых, на него могут подписаться не только люди, -но и машины. В нашем случае это Discord-бот [FeedCord](https://github.com/Qolors/FeedCord/). Ура, можно не городить свой воркфлоу, дёргающий [веб-хуки](https://discord.com/developers/docs/resources/webhook)! - -Поскольку SvelteKit — это fullstack-фреймворк, на нём же напишем простую ручку [rss.xml](https://teasanctuary.ru/blog/rss.xml). Это будет простая функция `GET()`, -возвращающая текст, ручками слепленный в что-то приблизительно похожее на XML. Но погодите, наш сайт же статичный, как мы будем вызывать эту JS-функцию? - -_Внимание, фокус!_ Мы берём обычный файл `src/routes/blog/rss.xml/+server.ts`, и добавляем в него волшебную строчку: - -```ts -export const prerender = true; -``` - -При сборке SvelteKit видит, что страницу можно сгенерировать заранее, выполняет весь этот код (а там то же самое, что в странице со списком блог-постов, но очевидно, в формате XML), -и в папке `build/` создаёт файл `rss.xml`. _Та-да!_ - -Конечно, это отвратительный хак, но мне честно не хотелось добавлять этап постобработки, у нас и так там стоит [генератор sitemap.xml](https://github.com/bartholomej/svelte-sitemap). -Да, ещё одна забытая технология. Надеюсь, хоть так [чёртовы ЖиПиТи-боты](https://git.teasanctuary.ru/TeaSanctuary/teasanctuary.ru/src/branch/master/static/robots.txt) перестанут -вслепую тыкаться по нашим сайтам, особенно по нашему инстансу Forgejo. - -## Деплой - -Сайт Small Fish, упомянутый в самом начале, располагается на GitHub Pages: CI/CD хук собирает сайт, запаковывает его в zip-архив и публикует на своём домене `.github.io`, -который ещё файлом `CNAME` можно поменять на абсолютно любой. - -Мы же хотим оставаться независимыми, в конце концов, зачем мы платим за серваки? Вот Nginx в контейнере, вот SFTP, закидывай и всё! - -Так и было, когда наш сайт был проще, _намного проще_ (умоляю вас, **не смотрите** в Web-архив!!!), но потом захотелось CD как у больших дядь. -На это есть [Forgejo Actions](https://forgejo.org/docs/latest/user/actions/reference/), местный аналог GitHub Actions. Пишешь скрипт, пушишь коммит, -всё будет сделано за тебя. Так, стоп, а почему не работает? Стоп, надо ещё хостить свой раннер? - -Да уж, совсем забыл упомянуть... На нашем основном сервере с доменом [teasanctuary.ru](https://teasanctuary.ru) всего **один гигабайт ОЗУ**. **Один**. Сюда еле влез Forgejo -с несколькими сервисами для нашего внутреннего пользования, а тут ещё надо запускать NodeJs, который запросто выжрет остаток и не подавится. - -Как быть?.. - -Помощь пришла откуда не ждали. Я, **rndtrash**, некоторое время назад переехал на другую квартиру, и пока я разбирал вещи, среди ящичков моему взору предстала ма-а-аленькая чёрная -коробочка с логотипом _некоторого пчелиного оператора_. Как позже выяснилось, это Smart TV-приставка, которую провайдер просто отказался забирать назад, ну а хозяевам квартиры она -просто не нужна. - -Меня накрыло в пот, и спустя один запрос в поисковике, один тред на 4PDA, один звонок владельцам квартиры и тридцать минут беготни в магазин электроники, -я понял, что передо мной лежал халявный сервер на Linux с ARMv8 процессором на 1.5 ГГц с одним гигабайтом оперативки. - -**ДЖЕКПОТ!!! 🎰🎰🎰** - -Дальше дело за малым: я просверлил несколько вентиляционных отверстий, чтобы коробка совсем не прокоптилась (вы бы видели местный охлад...), разрегистрировал её в нашем Forgejo, -и проработал план действий. Ввиду простоты сайта, наш [deploy-скрипт](https://git.teasanctuary.ru/TeaSanctuary/teasanctuary.ru/src/branch/master/.forgejo/workflows/deploy.yaml) -предельно прост: - -1. Ставим Ноду, клонируем репозиторий, устанавливаем Npm-зависимости -2. Кешируем зависимости, потому что это всё непотребство работает от _моего_ Интернета -3. Собираем сайт командой `npm run build` и формируем артефакт — архив с сайтом -4. Достаём SSH-ключ к основному серверу из секретного хранилища и регистрируем в SSH-агенте. Этот ключ имеет доступ только к особому пользователю, - который в свою очередь имеет доступ только к особой папке только для сайта -5. С помощью `rsync`, полностью заменяем содержимое удалённой папки на содержимое артефакта, сделанного на этапе **3**. - Это сделано на случай, если в репозитории случайно оказалось что-то непубличное👻 - -В этом плане есть только один прокол, причём очень важный. - -## SSL - -Тот самый заветный зелёный замочек у нас был с самого начала, но в какой-то момент он пропал. Покопавшись в логах [Certbot](https://certbot.eff.org/), мы с ужасом обнаружили, -что с переездом всего ПО в Podman-контейнер, он вот уже месяц как не мог запустить свой собственный сервер для сертификации. - -Вообще, процесс получения сертификата [Let's Encrypt](https://letsencrypt.org/) происходит вот так: некий бот раз в два-три месяца запускает свой собственный сервер на порту 80, -и с его помощью раздаёт особый файл-ключ по адресу `домен/.well-known/acme-challenge/случайные-буквы`. Главный сервер Let's Encrypt стучится по этому адресу, тем самым удостоверяется, -что к нему обратился действительно владелец домена, а не случайный прохожий, и наконец, регистрирует обновлённый файл сертификата. - -У нас опять всё не как у людей, поэтому мы сделали вот как: Certbot имеет особый режим, где он просто кладёт нужный файл, а дальше системный администратор как-то сам решает, каким -образом поднять 80 порт. Мы не можем закинуть этот файл прямо в папку сайта, потому что наш deploy-скрипт полностью уничтожает постороннее содержимое, поэтому мы сделали отдельную -папку, а Nginx в свою очередь сконфигурировали так, что все обращения по пути `/.well-known/acme-challenge` идут мимо папки сайта. - -## Финал - -Вот столько трудностей нам пришлось преодолеть, чтобы довести сайт хотя бы до такого состояния. Если что, на момент написания работает только главная страница, и собственно блоги. -Остаётся надеяться, что написание остальных страниц не составит особых усилий. - -Если вам понравился наш блог, то подписывайтесь на наш [RSS-фид](https://teasanctuary.ru/blog/rss.xml), и обязательно загляните в -[наше сообщество в Discord](https://teasanctuary.ru/discord). - -Ещё увидимся! - -- Команда Tea Sanctuary, 2025 diff --git a/src/blogs/test_unpublished.md b/src/blogs/test_unpublished.md deleted file mode 100644 index 4691141..0000000 --- a/src/blogs/test_unpublished.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -title: 'Тестовый блог' -date: -dateChanged: '2025-09-29' -description: 'Немного о самом сайте' -projects: ['ts-hldm'] ---- - -# ПРИВЕТ ! - -Добро пожаловать на наш первый блог-пост! - - diff --git a/src/lib/components/BlogCard.svelte b/src/lib/components/BlogCard.svelte deleted file mode 100644 index b40213d..0000000 --- a/src/lib/components/BlogCard.svelte +++ /dev/null @@ -1,158 +0,0 @@ - - - - - -
- {#if post.thumbnail} - {post.thumbnailAlt - {/if} - {#if isPostFresh} -
- {#if isPostUpdated} - ОБНОВЛЕНО - {:else} - НОВОЕ - {/if} -
- {/if} -
-
-
- - {#if post.dateChanged} - - {/if} -
- - - {blogPostTypeToString(type)} - -
-
- -

{post.title}

- - {#if post.description} -

{post.description}

- {/if} -
-
- - diff --git a/src/lib/components/DateWidget.svelte b/src/lib/components/DateWidget.svelte deleted file mode 100644 index 69ab90d..0000000 --- a/src/lib/components/DateWidget.svelte +++ /dev/null @@ -1,32 +0,0 @@ - - -
- - - {dateString - ? new Date(dateString).toLocaleString(undefined, { - month: 'short', - day: 'numeric', - year: 'numeric' - }) - : 'Не опубликован!'} - -
diff --git a/src/lib/components/HoverIcon.svelte b/src/lib/components/HoverIcon.svelte index bd0e374..7fbff3c 100644 --- a/src/lib/components/HoverIcon.svelte +++ b/src/lib/components/HoverIcon.svelte @@ -4,39 +4,29 @@ let className: string = ''; export { className as class }; export let src: string | null = null; - export let alt: string | undefined = undefined; + export let text: string; export let size: number = 32; export let black: boolean = false; - function isUrl(src?: string) { - return src?.startsWith('/') || src?.startsWith('http'); - } + let isUrl; + $: isUrl = src?.startsWith('/') || src?.startsWith('http'); - {#if src} - {#if isUrl(src)} - + {#if isUrl} + {text} {:else} {/if} {:else} - {alt ?? 'Без иконки'} + {text} {/if} - + diff --git a/src/lib/components/IconBlock.svelte b/src/lib/components/IconBlock.svelte deleted file mode 100644 index 1d4922a..0000000 --- a/src/lib/components/IconBlock.svelte +++ /dev/null @@ -1,18 +0,0 @@ - - -
-
- - {caption} -
-
- -
-
diff --git a/src/lib/components/Img.svelte b/src/lib/components/Img.svelte deleted file mode 100644 index fd7de17..0000000 --- a/src/lib/components/Img.svelte +++ /dev/null @@ -1,20 +0,0 @@ - - -
- {alt - {#if caption} -

{caption}

- {/if} -
diff --git a/src/lib/components/InfoBlock.svelte b/src/lib/components/InfoBlock.svelte deleted file mode 100644 index 7635b91..0000000 --- a/src/lib/components/InfoBlock.svelte +++ /dev/null @@ -1,12 +0,0 @@ - - - - - diff --git a/src/lib/components/MdsvexLayout.svelte b/src/lib/components/MdsvexLayout.svelte deleted file mode 100644 index 061e2b2..0000000 --- a/src/lib/components/MdsvexLayout.svelte +++ /dev/null @@ -1,11 +0,0 @@ - - - - -{@render children()} diff --git a/src/lib/components/NavBar.svelte b/src/lib/components/NavBar.svelte index 84e5596..3316ee0 100644 --- a/src/lib/components/NavBar.svelte +++ b/src/lib/components/NavBar.svelte @@ -20,7 +20,7 @@ diff --git a/src/lib/components/SocialButton.svelte b/src/lib/components/SocialButton.svelte index b18df8b..f12be28 100644 --- a/src/lib/components/SocialButton.svelte +++ b/src/lib/components/SocialButton.svelte @@ -14,7 +14,7 @@ target={isLinkLocal(href) ? '_self' : '_blank'} >
- +
= 40rem', false); - + - + diff --git a/src/lib/components/WarningBlock.svelte b/src/lib/components/WarningBlock.svelte deleted file mode 100644 index 50c9cf5..0000000 --- a/src/lib/components/WarningBlock.svelte +++ /dev/null @@ -1,12 +0,0 @@ - - - - - diff --git a/src/lib/util/Blogs.ts b/src/lib/util/Blogs.ts deleted file mode 100644 index 5878e26..0000000 --- a/src/lib/util/Blogs.ts +++ /dev/null @@ -1,81 +0,0 @@ -export const THUMBNAIL_DEFAULT = "https://teasanctuary.ru/common/background-day.webp"; -export const BLOG_POST_FRESHNESS_MILLIS = 3 * 24 * 60 * 60 * 1000; // 3 дня - -export type PostComparer = (a: App.BlogPost, b: App.BlogPost) => number; - -export const sortPostsByPostDate: PostComparer = (a, b) => new Date(b.date!).valueOf() - new Date(a.date!).valueOf(); - -function laterDate(a: string, b?: string): number { - const dateA = new Date(a).valueOf(); - if (!b) return dateA; - - const dateB = new Date(b).valueOf(); - return Math.max(dateA, dateB); -} - -export const sortPostsByPostAndUpdateDate: PostComparer = (a, b) => laterDate(b.date!, b.dateChanged) - laterDate(a.date!, a.dateChanged); - -export async function fetchPostsSorted(postComparer?: PostComparer) { - const allPosts = await fetchPosts(); - - const sortedPosts = allPosts - // Для списка постов оставляем только те, у которых объявлена дата публикации - .filter((a) => !!a.date) - .sort(postComparer ?? sortPostsByPostDate); - - return sortedPosts; -}; - -// rndtrash: пришлось заменить `path.parse`, так как на стороне клиента его больше не реализуют -function getFilenameFromPath(path: string) { - const file = path.split("/").pop(); - const dot = file?.lastIndexOf('.') ?? -1; - return dot === -1 ? file : file?.substring(0, file.lastIndexOf('.')); -} - -export async function fetchPosts() { - const allPostFiles = import.meta.glob('/src/blogs/*.md'); - const iterablePostFiles = Object.entries(allPostFiles); - - const allPosts: App.BlogPost[] = await Promise.all( - iterablePostFiles.map(async ([filePath, resolver]) => { - const { metadata }: any = await resolver(); - const name = getFilenameFromPath(filePath); - - return { - slug: name, - ...metadata - }; - }) - ); - - return allPosts; -}; - -export function resolveBlogPath(slug?: string, src?: string) { - if (!src) return null; - - return src.startsWith('http://') || src.startsWith('https://') - ? src - : `/blog/${slug}/${src}`; -} - -const bptToString: Record = { - 'article': 'Заметка', - 'event': 'Событие', - 'update': 'Обновление' -}; - -const bptToIcon: Record = { - 'article': 'material-symbols:article', - 'event': 'material-symbols:event', - 'update': 'material-symbols:autorenew' -}; - -export function blogPostTypeToString(type: App.BlogPostType): string { - return bptToString[type] ?? bptToString['article']; -} - -export function blogPostTypeToIcon(type: App.BlogPostType): string { - return bptToIcon[type] ?? bptToIcon['article']; -} diff --git a/src/lib/util/LinkResolver.ts b/src/lib/util/LinkResolver.ts index 9ada1da..9d1570e 100644 --- a/src/lib/util/LinkResolver.ts +++ b/src/lib/util/LinkResolver.ts @@ -1,4 +1,3 @@ -// TODO: чашки на иконках выглядят страшненько, пока что пусть лучше будет голая ссылка const icons: Record = { none: 'material-symbols:link', 'steamcommunity.com': 'simple-icons:steam', @@ -7,19 +6,17 @@ const icons: Record = { 'github.com': 'simple-icons:github', 'youtube.com': 'simple-icons:youtube', 'itch.io': 'simple-icons:itchdotio', - 'discord.com': 'simple-icons:discord', 'discord.gg': 'simple-icons:discord', 'gamebanana.com': 'simple-icons:gamebanana', 'bsky.app': 'simple-icons:bluesky', 'bsky.social': 'simple-icons:bluesky', // https://хамяк.рф 'xn--80auf8a2c.xn--p1ai': 'fluent-emoji-high-contrast:hamster', - // 'teasanctuary.ru': '/icons/tea-sanctuary-white.svg', + 'teasanctuary.ru': '/icons/tea-sanctuary-white.svg', 'hl.teasanctuary.ru': '/icons/half-life.svg', 'git.teasanctuary.ru': 'devicon-plain:git', - // localhost: '/icons/tea-sanctuary-white.svg', - email: 'material-symbols:alternate-email', - rss: 'material-symbols:rss-feed' + localhost: '/icons/tea-sanctuary-white.svg', + email: 'material-symbols:alternate-email' }; // Особые случаи, когда одним доменом второго уровня не ограничишься (например, randomtrash.itch.io) @@ -35,10 +32,6 @@ const specialResolvers: Record string> = { if (prefix[0] === "git") { return 'git.teasanctuary.ru'; } - // Особый случай: приглашение в Discord - if (url.pathname.startsWith('/discord')) { - return 'discord.gg'; - } return 'teasanctuary.ru'; }, // Игнорируем имя пользователя @@ -51,26 +44,24 @@ function getIconFromUrl(url: URL): string | undefined { const href = url.href; if (href.startsWith('mailto:')) return 'email'; - if (href.endsWith('/rss.xml') || href.endsWith('/atom.rss') || href.endsWith('.atom')) - return 'rss'; const hostname = url.hostname; const secondLevel = hostname.match(/(([A-Za-z0-9\-])+\.([A-Za-z0-9\-])+)$/)?.at(0) ?? ''; if (specialResolvers[secondLevel]) - return specialResolvers[secondLevel](url); + return icons[specialResolvers[secondLevel](url)]; - return hostname; + return icons[hostname]; } export function tryGetIcon(link: string): string { let url: URL; try { - url = new URL(link, document.baseURI); + url = new URL(link); } catch { return icons['none']; } - return icons[getIconFromUrl(url) ?? ''] ?? icons['none']; + return getIconFromUrl(url) ?? icons['none']; } /** diff --git a/src/pages/index.md b/src/pages/index.md deleted file mode 100644 index e206d50..0000000 --- a/src/pages/index.md +++ /dev/null @@ -1,39 +0,0 @@ -# Кто мы? - -__Tea Sanctuary__ — это в первую очередь коллектив друзей, разрабатывающих -проекты для души, для всеобщего пользования и даже на заказ. С -__8 июля 2017 года__ мы ведём публичную деятельность в сфере разработки ПО и развлечений. - -__Tea Sanctuary__ — это также и сообщество единомышленников. Любовь к добротным -видеоиграм и пассивная агрессия к вычислительной технике у нас в крови. Когда-то сообщество -было закрытым и насчитывало около 50 участников, но впоследствии мы решили его расширить. -Станьте частью коллектива! - -# Что делаем? - -Наша главная страсть — это, конечно, видеоигры. Мы часто участвуем в так -называемых "гейм джемах" — конкурсах на разработку игр. Наши игры вы можете -оценить здесь: [RandomTrash](https://randomtrash.itch.io) -[FriendlyWithMeat](https://friendlywithmeat.itch.io). -Также мы ведём работу над нашим первым полноценным игровым проектом. -Следите за новостями в нашем [сообществе](https://teasanctuary.ru/discord)! - -Отдельные участники нашего коллектива занимаются модификацией существующих игр, -добавляя в них новый контент. Например, __MegaZerg__ создаёт оригинальные карты -для такой бессмертной классики, как __Counter-Strike 1.6__ и __Half-Life Deathmatch__, -и выкладывает их на ресурс GameBanana: [kemist](https://gamebanana.com/members/2971042) - -Мы размещаем игровые сервера, как постоянные, так и временные для различных событий. -Например, у нас есть сервер [Tea Sanctuary HLDM](https://hl.teasanctuary.ru), -где вы можете ознакомиться с новыми картами от всего сообщества Half-Life. - -Не одними играми едины, за нашими плечами есть несколько прикладных программ, -созданных под заказ. Про них ничего особо рассказать не можем, но если вам надо -что-нибудь сделать — пишите нам! - -# Как с вами связаться? - -Общие вопросы можно задавать в [сообществе Tea Sanctuary](https://teasanctuary.ru/discord). -Там же можете написать личное сообщение администраторам. - -Наши соцсети и почту для более важных обращений можно найти на странице [Контакты](/contact). diff --git a/src/routes/+error.svelte b/src/routes/+error.svelte index 58bd3e5..0f5e7ab 100644 --- a/src/routes/+error.svelte +++ b/src/routes/+error.svelte @@ -7,7 +7,7 @@
diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index fafc4f0..4b33d76 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -1,7 +1,6 @@ - {#if page.data.title !== undefined} - {page.data.title} — Tea Sanctuary - - {:else} - Tea Sanctuary - - {/if} - - + + + + + Tea Sanctuary -
+
diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index da806ec..fab3d9e 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -1,18 +1,16 @@ Tea Sanctuary -
+
@@ -59,12 +57,19 @@ >
-
+

TEA

SANCTUARY

+
Сообщество @@ -82,61 +87,69 @@
- -
-

ПОСЛЕДНИЕ ПОСТЫ

- -
- {#each page.data.posts as post, i} -
- +
+
+
+

Кто мы?

+
+ Tea Sanctuary — это в первую очередь коллектив друзей, разрабатывающих проекты + для души, для всеобщего пользования и даже на заказ. С 8 июля 2017 года мы ведём публичную + деятельность в сфере разработки ПО и развлечений.
- {/each} +
+
+ Tea Sanctuary — это также и сообщество единомышленников. Любовь к добротным видеоиграм + и пассивная агрессия к вычислительной технике у нас в крови. Когда-то сообщество было закрытым + и насчитывало около 50 участников, но впоследствии мы решили его расширить. Станьте частью коллектива! +
+
+
+

Что делаем?

+
+ Наша главная страсть — это, конечно, видеоигры. Мы часто участвуем в так называемых + "гейм джемах" — конкурсах на разработку игр. Наши игры вы можете оценить здесь: + RandomTrash + FriendlyWithMeat. + Также мы ведём работу над нашим первым полноценным игровым проектом. + Следите за новостями в нашем + сообществе! +
+
+
+ Отдельные участники нашего коллектива занимаются модификацией существующих игр, добавляя в + них новый контент. Например, MegaZerg создаёт оригинальные карты для такой + бессмертной классики, как Counter-Strike 1.6, и выкладывает их на ресурс GameBanana: + kemist +
+
+
+ Мы размещаем игровые сервера, как постоянные, так и временные для различных событий. + Например, у нас есть сервер + Tea Sanctuary HLDM, где + вы можете ознакомиться с новыми картами от всего сообщества Half-Life. +
+
+
+ Не одними играми едины, за нашими плечами есть несколько прикладных программ, созданных под + заказ. Про них ничего особо рассказать не можем, но если вам надо что-нибудь сделать — + пишите нам! +
+
+
+

Как с вами связаться?

+
+ TODO: контакты. +
+
-
-
- -
-
- + \ No newline at end of file diff --git a/src/routes/+page.ts b/src/routes/+page.ts deleted file mode 100644 index 1c87cc4..0000000 --- a/src/routes/+page.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { fetchPostsSorted, sortPostsByPostAndUpdateDate } from "$src/lib/util/Blogs"; - -const LATEST_POSTS_COUNT = 3; - -export async function load() { - let md: any - try { - md = await import("$src/pages/index.md"); - } catch (ex) { - throw "Не удалось найти текст для главной страницы"; - } - console.log("TEST", md.default); - return { - content: md.default, - posts: (await fetchPostsSorted(sortPostsByPostAndUpdateDate)).slice(0, LATEST_POSTS_COUNT) - }; -} \ No newline at end of file diff --git a/src/routes/blog/+page.server.ts b/src/routes/blog/+page.server.ts deleted file mode 100644 index c5653b5..0000000 --- a/src/routes/blog/+page.server.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { fetchPostsSorted } from "$src/lib/util/Blogs"; - -export async function load() { - return { title: "Блог", description: "Новости и заметки проектов Tea Sanctuary", posts: await fetchPostsSorted() }; -} \ No newline at end of file diff --git a/src/routes/blog/+page.svelte b/src/routes/blog/+page.svelte index 870fcc9..f61e340 100644 --- a/src/routes/blog/+page.svelte +++ b/src/routes/blog/+page.svelte @@ -1,28 +1,14 @@ -
+ + Блог — Tea Sanctuary + + +
@@ -34,26 +20,6 @@
- - Подпишитесь на нашу RSS ленту, чтобы не - пропускать новые посты! - - -
- {#each groupedPosts.entries() as [monthYear, postsInMonthYear]} -

- {monthYear} -

-
- {#each postsInMonthYear as post, i} - - {/each} -
- {/each} -
- + @import "$src/app.css"; + \ No newline at end of file diff --git a/src/routes/blog/[slug]/+page.svelte b/src/routes/blog/[slug]/+page.svelte deleted file mode 100644 index 8c64572..0000000 --- a/src/routes/blog/[slug]/+page.svelte +++ /dev/null @@ -1,100 +0,0 @@ - - - - - - - - -
-
-
-

{data.blogPost.title}

- {#if data.blogPost.description} -

{data.blogPost.description}

- {/if} -
-
-
- -
- - {#if data.blogPost.dateChanged} - - {/if} -
- - - {blogPostTypeToString(type)} - -
- {#each authors as author} - - - - - {author} - - - {/each} -
- -{#if page.data.blogPost.projects?.length > 0} - -

В данной заметке упоминаются наши проекты:

-
    - {#each page.data.blogPost.projects as project} -
  • {project}
  • - {/each} -
-
-{/if} - -
- -
- - diff --git a/src/routes/blog/[slug]/+page.ts b/src/routes/blog/[slug]/+page.ts deleted file mode 100644 index 46b041c..0000000 --- a/src/routes/blog/[slug]/+page.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { resolveBlogPath, THUMBNAIL_DEFAULT } from "$src/lib/util/Blogs.js"; -import { error } from "@sveltejs/kit"; - -export async function load({ params }) { - let post: any - try { - post = await import(`$src/blogs/${params.slug}.md`); - } catch (ex) { - error(404); - } - const blogPost: App.BlogPost = post.metadata; - const thumbnail = resolveBlogPath(params.slug, blogPost.thumbnail ?? THUMBNAIL_DEFAULT); - - return { - title: `${blogPost.title} — Блог`, - description: blogPost.description, - thumbnail: thumbnail, - content: post.default, - blogPost: { - ...blogPost - } - }; -}; diff --git a/src/routes/blog/rss.xml/+server.ts b/src/routes/blog/rss.xml/+server.ts deleted file mode 100644 index 59429cf..0000000 --- a/src/routes/blog/rss.xml/+server.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { fetchPostsSorted, resolveBlogPath } from "$src/lib/util/Blogs"; - -export const prerender = true; - -const feedUpdated = new Date(); - -function escapeXml(unsafe: string): string { - return unsafe - .replace(/&/g, "&") - .replace(//g, ">") - .replace(/"/g, """) - .replace(/'/g, "'"); -} - -function makeThumbnail(post: App.BlogPost): string { - if (!post.thumbnail) return ''; - - const alt = !!post.thumbnailAlt ? ` alt="${escapeXml(post.thumbnailAlt)}"` : ''; - - return `

`; -} - -function makeAuthors(post: App.BlogPost): string { - const authors = - (post.authors == null - ? [] - : typeof post.authors === 'string' - ? [post.authors] - : post.authors) - .map(a => escapeXml(a)); - - if (authors.length === 0) return ''; - - let authorsString = authors[0]; - if (authors.length > 1) { - const lastAuthor = authors.pop(); - authorsString = `${authors.join(', ')} и ${lastAuthor}`; - } - - return `\n${authorsString}`; -} - -export async function GET({ setHeaders }) { - setHeaders({ - 'Cache-Control': 'max-age=0, s-maxage=3600', - 'Content-Type': 'application/rss+xml', - }); - const posts = await fetchPostsSorted(); - return new Response(String(` - - -Блог Tea Sanctuary -https://teasanctuary.ru/blog -1800 -${feedUpdated.toUTCString()} -${posts.map((post) => ` -${escapeXml(post.title)} -${makeAuthors(post)} -https://teasanctuary.ru/blog/${post.slug} -https://teasanctuary.ru/blog/${post.slug} -${(new Date(post.date!)).toUTCString()} -`).join("\n")} - -`)) -} \ No newline at end of file diff --git a/src/routes/contact/+page.svelte b/src/routes/contact/+page.svelte index e1aa212..9be477d 100644 --- a/src/routes/contact/+page.svelte +++ b/src/routes/contact/+page.svelte @@ -1,9 +1,14 @@ -
+ + Контакты — Tea Sanctuary + + +
@@ -15,35 +20,6 @@
-Страница находится в разработке! - -
-
-
- На данный момент вы можете связаться с администрацией сайта и участниками команды через - - нашу гильдию в Discord - : -
- -
- Вы также можете ознакомиться с социальными сетями каждого отдельного участника команды - на странице Команда. -
-
-
- + @import "$src/app.css"; + \ No newline at end of file diff --git a/src/routes/contact/+page.ts b/src/routes/contact/+page.ts deleted file mode 100644 index 2d33459..0000000 --- a/src/routes/contact/+page.ts +++ /dev/null @@ -1,3 +0,0 @@ -export async function load() { - return { title: "Контакты" }; -} \ No newline at end of file diff --git a/src/routes/projects/+page.svelte b/src/routes/projects/+page.svelte index fa92579..692a7cc 100644 --- a/src/routes/projects/+page.svelte +++ b/src/routes/projects/+page.svelte @@ -1,8 +1,14 @@ -
+ + Проекты — Tea Sanctuary + + +
@@ -14,8 +20,6 @@
-Страница находится в разработке! - + @import "$src/app.css"; + \ No newline at end of file diff --git a/src/routes/projects/+page.ts b/src/routes/projects/+page.ts deleted file mode 100644 index efd7981..0000000 --- a/src/routes/projects/+page.ts +++ /dev/null @@ -1,3 +0,0 @@ -export async function load() { - return { title: "Проекты" }; -} \ No newline at end of file diff --git a/src/routes/team/+page.svelte b/src/routes/team/+page.svelte index 155b80b..94b4472 100644 --- a/src/routes/team/+page.svelte +++ b/src/routes/team/+page.svelte @@ -1,8 +1,14 @@ -
+ + Команда — Tea Sanctuary + + +
@@ -14,8 +20,6 @@
-Страница находится в разработке! - + @import "$src/app.css"; + \ No newline at end of file diff --git a/src/routes/team/+page.ts b/src/routes/team/+page.ts deleted file mode 100644 index d0b3bdc..0000000 --- a/src/routes/team/+page.ts +++ /dev/null @@ -1,3 +0,0 @@ -export async function load() { - return { title: "Команда" }; -} \ No newline at end of file diff --git a/static/blog/hello_world/wave.png b/static/blog/hello_world/wave.png deleted file mode 100644 index 5eac6a0..0000000 Binary files a/static/blog/hello_world/wave.png and /dev/null differ diff --git a/static/blog/test_unpublished/wasd_perelesoq_game_jam_2025.png b/static/blog/test_unpublished/wasd_perelesoq_game_jam_2025.png deleted file mode 100644 index dd59dc2..0000000 Binary files a/static/blog/test_unpublished/wasd_perelesoq_game_jam_2025.png and /dev/null differ diff --git a/static/favicon.ico b/static/favicon.ico deleted file mode 100644 index 6d27e04..0000000 Binary files a/static/favicon.ico and /dev/null differ diff --git a/static/robots.txt b/static/robots.txt index fbbfa75..d49c9b4 100644 --- a/static/robots.txt +++ b/static/robots.txt @@ -6,8 +6,6 @@ User-agent: AhrefsBot Disallow: / User-agent: MJ12bot Disallow: / -User-agent: GPTBot -Disallow: / User-agent: * Allow: / diff --git a/svelte.config.js b/svelte.config.js index 2ea3e94..ebf0dc8 100644 --- a/svelte.config.js +++ b/svelte.config.js @@ -1,13 +1,8 @@ import adapter from '@sveltejs/adapter-static'; import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; import { mdsvex } from 'mdsvex'; +import mdsvexConfig from './mdsvex.config.js'; import autoImport from 'sveltekit-autoimport'; -import { join, dirname } from 'path'; -import { fileURLToPath } from 'url'; - -// https://flaviocopes.com/fix-dirname-not-defined-es-module-scope/ -const __filename = fileURLToPath(import.meta.url); -const __dirname = dirname(__filename); /** @type {import('@sveltejs/kit').Config} */ const config = { @@ -38,11 +33,8 @@ const config = { }, extensions: ['.svelte', '.md'], preprocess: [ - vitePreprocess({ script: true }), - mdsvex({ - extensions: ['.md'], - layout: join(__dirname, "./src/lib/components/MdsvexLayout.svelte") - }), + vitePreprocess(), + mdsvex(mdsvexConfig), autoImport({ include: ['**/*.(svelte|md)'], components: ['./src/lib/components/', { name: './src' }] diff --git a/tailwind.config.ts b/tailwind.config.ts index 1954591..93dc09d 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -1,12 +1,45 @@ +import plugin from 'tailwindcss/plugin'; + /** @type {import('tailwindcss').Config} */ export default { content: ['./src/**/*.{html,js,svelte,ts}'], theme: { + // colors: { + // white: '#FFFFFF', + // transparentblue: '#2447f779', + // blue: '#2446f7', + // black: '#000000', + // transparentblack1: '#000000BB', + // transparentblack0: '#00000011', + // darkblue: '#091856', + // navyblue: '#0f2898', + // gray: '#e2e2e2', + // lightblue: '#0092ff', + // transparent: 'transparent' + // }, extend: { fontFamily: { sans: ['Lineyka', 'sans-serif'], disket: ['Disket Mono', 'monospace'], }, + // height: { + // // 64 px for navbar + // screen: 'calc(100vh - 64px)' + // }, + // dropShadow: { + // md: '0px 0px 2px #091856', + // hover: '0px 6px 2px #091856' + // }, + // backgroundImage: { + // pixel: "url('/common/pixel-overlay.png')", + // 'pixel-dark': "url('/common/pixel-overlay-dark.png')", + // 'pixel-white': "url('/common/pixel-overlay-white.png')", + // 'pixel-large': "url('/common/pixel-overlay-large.png')" + // }, + // backgroundSize: { + // pixel: '7px', + // 'pixel-lg': '14px' + // }, typography: { DEFAULT: { css: {