Merge pull request #6 from tmshv/feature/upd1

Update 1
This commit is contained in:
tmshv authored and GitHub committed 2019-10-17 17:06:17 +03:00
commit 0fab398ca6
123 files changed
+2521 -2287

No files matched your search

-17
View File
@@ -1,17 +0,0 @@
{
"presets": [
[
"env",
{
"targets": {
"node": "8.5.0"
}
}
]
],
"plugins": [
"transform-react-jsx",
"@babel/plugin-proposal-object-rest-spread",
"transform-class-properties"
]
}
+2
View File
@@ -0,0 +1,2 @@
/// <reference types="next" />
/// <reference types="next/types/global" />
+370 -16
View File
@@ -1529,27 +1529,124 @@
}
},
"@babel/plugin-transform-typescript": {
"version": "7.5.2",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.5.2.tgz",
"integrity": "sha512-r4zJOMbKY5puETm8+cIpaa0RQZG/sSASW1u0pj8qYklcERgVIbxVbP2wyJA7zI1//h7lEagQmXi9IL9iI5rfsA==",
"version": "7.6.3",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.6.3.tgz",
"integrity": "sha512-aiWINBrPMSC3xTXRNM/dfmyYuPNKY/aexYqBgh0HBI5Y+WO5oRAqW/oROYeYHrF4Zw12r9rK4fMk/ZlAmqx/FQ==",
"requires": {
"@babel/helper-create-class-features-plugin": "^7.5.0",
"@babel/helper-create-class-features-plugin": "^7.6.0",
"@babel/helper-plugin-utils": "^7.0.0",
"@babel/plugin-syntax-typescript": "^7.2.0"
},
"dependencies": {
"@babel/code-frame": {
"version": "7.5.5",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.5.5.tgz",
"integrity": "sha512-27d4lZoomVyo51VegxI20xZPuSHusqbQag/ztrBC7wegWoQ1nLREPVSKSW8byhTlzTKyNE4ifaTA6lCp7JjpFw==",
"requires": {
"@babel/highlight": "^7.0.0"
}
},
"@babel/generator": {
"version": "7.6.4",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.6.4.tgz",
"integrity": "sha512-jsBuXkFoZxk0yWLyGI9llT9oiQ2FeTASmRFE32U+aaDTfoE92t78eroO7PTpU/OrYq38hlcDM6vbfLDaOLy+7w==",
"requires": {
"@babel/types": "^7.6.3",
"jsesc": "^2.5.1",
"lodash": "^4.17.13",
"source-map": "^0.5.0"
}
},
"@babel/helper-create-class-features-plugin": {
"version": "7.5.0",
"resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.5.0.tgz",
"integrity": "sha512-EAoMc3hE5vE5LNhMqDOwB1usHvmRjCDAnH8CD4PVkX9/Yr3W/tcz8xE8QvdZxfsFBDICwZnF2UTHIqslRpvxmA==",
"version": "7.6.0",
"resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.6.0.tgz",
"integrity": "sha512-O1QWBko4fzGju6VoVvrZg0RROCVifcLxiApnGP3OWfWzvxRZFCoBD81K5ur5e3bVY2Vf/5rIJm8cqPKn8HUJng==",
"requires": {
"@babel/helper-function-name": "^7.1.0",
"@babel/helper-member-expression-to-functions": "^7.0.0",
"@babel/helper-member-expression-to-functions": "^7.5.5",
"@babel/helper-optimise-call-expression": "^7.0.0",
"@babel/helper-plugin-utils": "^7.0.0",
"@babel/helper-replace-supers": "^7.4.4",
"@babel/helper-replace-supers": "^7.5.5",
"@babel/helper-split-export-declaration": "^7.4.4"
}
},
"@babel/helper-member-expression-to-functions": {
"version": "7.5.5",
"resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.5.5.tgz",
"integrity": "sha512-5qZ3D1uMclSNqYcXqiHoA0meVdv+xUEex9em2fqMnrk/scphGlGgg66zjMrPJESPwrFJ6sbfFQYUSa0Mz7FabA==",
"requires": {
"@babel/types": "^7.5.5"
}
},
"@babel/helper-replace-supers": {
"version": "7.5.5",
"resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.5.5.tgz",
"integrity": "sha512-XvRFWrNnlsow2u7jXDuH4jDDctkxbS7gXssrP4q2nUD606ukXHRvydj346wmNg+zAgpFx4MWf4+usfC93bElJg==",
"requires": {
"@babel/helper-member-expression-to-functions": "^7.5.5",
"@babel/helper-optimise-call-expression": "^7.0.0",
"@babel/traverse": "^7.5.5",
"@babel/types": "^7.5.5"
}
},
"@babel/parser": {
"version": "7.6.4",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.6.4.tgz",
"integrity": "sha512-D8RHPW5qd0Vbyo3qb+YjO5nvUVRTXFLQ/FsDxJU2Nqz4uB5EnUN0ZQSEYpvTIbRuttig1XbHWU5oMeQwQSAA+A=="
},
"@babel/traverse": {
"version": "7.6.3",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.6.3.tgz",
"integrity": "sha512-unn7P4LGsijIxaAJo/wpoU11zN+2IaClkQAxcJWBNCMS6cmVh802IyLHNkAjQ0iYnRS3nnxk5O3fuXW28IMxTw==",
"requires": {
"@babel/code-frame": "^7.5.5",
"@babel/generator": "^7.6.3",
"@babel/helper-function-name": "^7.1.0",
"@babel/helper-split-export-declaration": "^7.4.4",
"@babel/parser": "^7.6.3",
"@babel/types": "^7.6.3",
"debug": "^4.1.0",
"globals": "^11.1.0",
"lodash": "^4.17.13"
}
},
"@babel/types": {
"version": "7.6.3",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.6.3.tgz",
"integrity": "sha512-CqbcpTxMcpuQTMhjI37ZHVgjBkysg5icREQIEZ0eG1yCNwg3oy+5AaLiOKmjsCj6nqOsa6Hf0ObjRVwokb7srA==",
"requires": {
"esutils": "^2.0.2",
"lodash": "^4.17.13",
"to-fast-properties": "^2.0.0"
}
},
"debug": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
"integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
"requires": {
"ms": "^2.1.1"
}
},
"globals": {
"version": "11.12.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
"integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA=="
},
"jsesc": {
"version": "2.5.2",
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz",
"integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA=="
},
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
"to-fast-properties": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
"integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4="
}
}
},
@@ -1720,12 +1817,13 @@
}
},
"@babel/preset-typescript": {
"version": "7.3.3",
"resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.3.3.tgz",
"integrity": "sha512-mzMVuIP4lqtn4du2ynEfdO0+RYcslwrZiJHXu4MGaC1ctJiW2fyaeDrtjJGs7R/KebZ1sgowcIoWf4uRpEfKEg==",
"version": "7.6.0",
"resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.6.0.tgz",
"integrity": "sha512-4xKw3tTcCm0qApyT6PqM9qniseCE79xGHiUnNdKGdxNsGUc2X7WwZybqIpnTmoukg3nhPceI5KPNzNqLNeIJww==",
"dev": true,
"requires": {
"@babel/helper-plugin-utils": "^7.0.0",
"@babel/plugin-transform-typescript": "^7.3.2"
"@babel/plugin-transform-typescript": "^7.6.0"
}
},
"@babel/register": {
@@ -1862,12 +1960,187 @@
}
}
},
"@types/accepts": {
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/@types/accepts/-/accepts-1.3.5.tgz",
"integrity": "sha512-jOdnI/3qTpHABjM5cx1Hc0sKsPoYCp+DP/GJRGtDlPd7fiV9oXGGIcjW/ZOxLIvjGz8MA+uMZI9metHlgqbgwQ==",
"dev": true,
"requires": {
"@types/node": "*"
}
},
"@types/body-parser": {
"version": "1.17.1",
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.17.1.tgz",
"integrity": "sha512-RoX2EZjMiFMjZh9lmYrwgoP9RTpAjSHiJxdp4oidAQVO02T7HER3xj9UKue5534ULWeqVEkujhWcyvUce+d68w==",
"dev": true,
"requires": {
"@types/connect": "*",
"@types/node": "*"
}
},
"@types/bson": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/@types/bson/-/bson-4.0.0.tgz",
"integrity": "sha512-pq/rqJwJWkbS10crsG5bgnrisL8pML79KlMKQMoQwLUjlPAkrUHMvHJ3oGwE7WHR61Lv/nadMwXVAD2b+fpD8Q==",
"dev": true,
"requires": {
"@types/node": "*"
}
},
"@types/classnames": {
"version": "2.2.9",
"resolved": "https://registry.npmjs.org/@types/classnames/-/classnames-2.2.9.tgz",
"integrity": "sha512-MNl+rT5UmZeilaPxAVs6YaPC2m6aA8rofviZbhbxpPpl61uKodfdQVsBtgJGTqGizEf02oW3tsVe7FYB8kK14A==",
"dev": true
},
"@types/connect": {
"version": "3.4.32",
"resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.32.tgz",
"integrity": "sha512-4r8qa0quOvh7lGD0pre62CAb1oni1OO6ecJLGCezTmhQ8Fz50Arx9RUszryR8KlgK6avuSXvviL6yWyViQABOg==",
"dev": true,
"requires": {
"@types/node": "*"
}
},
"@types/cookies": {
"version": "0.7.4",
"resolved": "https://registry.npmjs.org/@types/cookies/-/cookies-0.7.4.tgz",
"integrity": "sha512-oTGtMzZZAVuEjTwCjIh8T8FrC8n/uwy+PG0yTvQcdZ7etoel7C7/3MSd7qrukENTgQtotG7gvBlBojuVs7X5rw==",
"dev": true,
"requires": {
"@types/connect": "*",
"@types/express": "*",
"@types/keygrip": "*",
"@types/node": "*"
}
},
"@types/express": {
"version": "4.17.1",
"resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.1.tgz",
"integrity": "sha512-VfH/XCP0QbQk5B5puLqTLEeFgR8lfCJHZJKkInZ9mkYd+u8byX0kztXEQxEk4wZXJs8HI+7km2ALXjn4YKcX9w==",
"dev": true,
"requires": {
"@types/body-parser": "*",
"@types/express-serve-static-core": "*",
"@types/serve-static": "*"
}
},
"@types/express-serve-static-core": {
"version": "4.16.9",
"resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.16.9.tgz",
"integrity": "sha512-GqpaVWR0DM8FnRUJYKlWgyARoBUAVfRIeVDZQKOttLFp5SmhhF9YFIYeTPwMd/AXfxlP7xVO2dj1fGu0Q+krKQ==",
"dev": true,
"requires": {
"@types/node": "*",
"@types/range-parser": "*"
}
},
"@types/http-assert": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/@types/http-assert/-/http-assert-1.5.1.tgz",
"integrity": "sha512-PGAK759pxyfXE78NbKxyfRcWYA/KwW17X290cNev/qAsn9eQIxkH4shoNBafH37wewhDG/0p1cHPbK6+SzZjWQ==",
"dev": true
},
"@types/keygrip": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@types/keygrip/-/keygrip-1.0.1.tgz",
"integrity": "sha1-/1QEYtL7TQqIRBzq8n0oewHD2Hg=",
"dev": true
},
"@types/koa": {
"version": "2.0.50",
"resolved": "https://registry.npmjs.org/@types/koa/-/koa-2.0.50.tgz",
"integrity": "sha512-TcgOD2lh0EISSadAk1DOBYw7kNoY9XdeB3vEMOKiDDaTMYm+V54nyPsU7Ulb/htb5OBIR79RgTeCWntCcophLw==",
"dev": true,
"requires": {
"@types/accepts": "*",
"@types/cookies": "*",
"@types/http-assert": "*",
"@types/keygrip": "*",
"@types/koa-compose": "*",
"@types/node": "*"
}
},
"@types/koa-compose": {
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/@types/koa-compose/-/koa-compose-3.2.4.tgz",
"integrity": "sha512-ioou0rxkuWL+yBQYsHUQAzRTfVxAg8Y2VfMftU+Y3RA03/MzuFL0x/M2sXXj3PkfnENbHsjeHR1aMdezLYpTeA==",
"dev": true,
"requires": {
"@types/koa": "*"
}
},
"@types/koa-route": {
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/@types/koa-route/-/koa-route-3.2.4.tgz",
"integrity": "sha512-jB4I4/HppiHiSFMqQvMC2Px5Zm8CExA7Bw7xW+o60xh4v0LrNTfKV7LMCGuJNRuUuTTiRcJvF1rbq5mINgQ1UQ==",
"dev": true,
"requires": {
"@types/koa": "*",
"path-to-regexp": "^1.7.0"
}
},
"@types/mime": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-2.0.1.tgz",
"integrity": "sha512-FwI9gX75FgVBJ7ywgnq/P7tw+/o1GUbtP0KzbtusLigAOgIgNISRK0ZPl4qertvXSIE8YbsVJueQ90cDt9YYyw==",
"dev": true
},
"@types/mongodb": {
"version": "3.3.6",
"resolved": "https://registry.npmjs.org/@types/mongodb/-/mongodb-3.3.6.tgz",
"integrity": "sha512-bPv0lGnkL/iOoA+GSAcS5ad9QyPc/rW547vrF5j+AKX4spaMZgpaCWQ4D6ipVSN6OKuks3ueCFEPinyJJSk2zA==",
"dev": true,
"requires": {
"@types/bson": "*",
"@types/node": "*"
}
},
"@types/node": {
"version": "12.7.12",
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.7.12.tgz",
"integrity": "sha512-KPYGmfD0/b1eXurQ59fXD1GBzhSQfz6/lKBxkaHX9dKTzjXbK68Zt7yGUxUsCS1jeTy/8aL+d9JEr+S54mpkWQ==",
"dev": true
},
"@types/prop-types": {
"version": "15.7.3",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.3.tgz",
"integrity": "sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==",
"dev": true
},
"@types/q": {
"version": "1.5.2",
"resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.2.tgz",
"integrity": "sha512-ce5d3q03Ex0sy4R14722Rmt6MT07Ua+k4FwDfdcToYJcMKNtRVQvJ6JCAPdAmAnbRb6CsX6aYb9m96NGod9uTw==",
"dev": true
},
"@types/range-parser": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.3.tgz",
"integrity": "sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA==",
"dev": true
},
"@types/react": {
"version": "16.9.5",
"resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.5.tgz",
"integrity": "sha512-jQ12VMiFOWYlp+j66dghOWcmDDwhca0bnlcTxS4Qz/fh5gi6wpaZDthPEu/Gc/YlAuO87vbiUXL8qKstFvuOaA==",
"dev": true,
"requires": {
"@types/prop-types": "*",
"csstype": "^2.2.0"
}
},
"@types/serve-static": {
"version": "1.13.3",
"resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.3.tgz",
"integrity": "sha512-oprSwp094zOglVrXdlo/4bAHtKTAxX6VT8FOZlBKrmyLbNvE1zxZyJ6yikMVtHIvwP45+ZQGJn+FdXGKTozq0g==",
"dev": true,
"requires": {
"@types/express-serve-static-core": "*",
"@types/mime": "*"
}
},
"@webassemblyjs/ast": {
"version": "1.8.5",
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.8.5.tgz",
@@ -2282,6 +2555,12 @@
"readable-stream": "^2.0.6"
}
},
"arg": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/arg/-/arg-4.1.1.tgz",
"integrity": "sha512-SlmP3fEA88MBv0PypnXZ8ZfJhwmDeIE3SP71j37AiXQBXYosPV0x6uISAaHYSlSVhmHOVkomen0tbGk6Anlebw==",
"dev": true
},
"argparse": {
"version": "1.0.9",
"resolved": "http://storage.mds.yandex.net/get-npm/69187/argparse-1.0.9.tgz",
@@ -2559,9 +2838,9 @@
},
"dependencies": {
"is-buffer": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.3.tgz",
"integrity": "sha512-U15Q7MXTuZlrbymiz95PJpZxu8IlipAp4dtS3wOdgPXx3mqBnslrWU14kxfHB+Py/+2PVKSr37dMAgM2A4uArw=="
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.4.tgz",
"integrity": "sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A=="
}
}
},
@@ -5235,6 +5514,12 @@
}
}
},
"csstype": {
"version": "2.6.7",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.7.tgz",
"integrity": "sha512-9Mcn9sFbGBAdmimWb2gLVDtFJzeKtDGIr76TUqmjZrw9LFXBMSU70lcs+C0/7fyCd6iBDqmksUcCOUIkisPHsQ==",
"dev": true
},
"currently-unhandled": {
"version": "0.4.1",
"resolved": "http://storage.mds.yandex.net/get-npm/38095/currently-unhandled-0.4.1.tgz",
@@ -8415,6 +8700,12 @@
}
}
},
"make-error": {
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.5.tgz",
"integrity": "sha512-c3sIjNUow0+8swNwVpqoH4YCShKNFkMaw6oH1mNS2haDZQqkeZFlHS3dhoeEbKKmJB4vXpJucU6oH75aDYeE9g==",
"dev": true
},
"mamacro": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/mamacro/-/mamacro-0.0.3.tgz",
@@ -9346,6 +9637,15 @@
"worker-farm": "1.7.0"
},
"dependencies": {
"@babel/preset-typescript": {
"version": "7.3.3",
"resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.3.3.tgz",
"integrity": "sha512-mzMVuIP4lqtn4du2ynEfdO0+RYcslwrZiJHXu4MGaC1ctJiW2fyaeDrtjJGs7R/KebZ1sgowcIoWf4uRpEfKEg==",
"requires": {
"@babel/helper-plugin-utils": "^7.0.0",
"@babel/plugin-transform-typescript": "^7.3.2"
}
},
"acorn": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-6.2.0.tgz",
@@ -15541,6 +15841,43 @@
"integrity": "sha1-OTvnMKlEb9Hq1tpZoBQwjzbCics=",
"dev": true
},
"ts-node": {
"version": "8.4.1",
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-8.4.1.tgz",
"integrity": "sha512-5LpRN+mTiCs7lI5EtbXmF/HfMeCjzt7DH9CZwtkr6SywStrNQC723wG+aOWFiLNn7zT3kD/RnFqi3ZUfr4l5Qw==",
"dev": true,
"requires": {
"arg": "^4.1.0",
"diff": "^4.0.1",
"make-error": "^1.1.1",
"source-map-support": "^0.5.6",
"yn": "^3.0.0"
},
"dependencies": {
"diff": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.1.tgz",
"integrity": "sha512-s2+XdvhPCOF01LRQBC8hf4vhbVmI2CGS5aZnxLJlT5FtdhPCDFq80q++zK2KlrVorVDdL5BOGZ/VfLrVtYNF+Q==",
"dev": true
},
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true
},
"source-map-support": {
"version": "0.5.13",
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz",
"integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==",
"dev": true,
"requires": {
"buffer-from": "^1.0.0",
"source-map": "^0.6.0"
}
}
}
},
"tslib": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz",
@@ -15594,6 +15931,12 @@
"resolved": "http://storage.mds.yandex.net/get-npm/35308/typedarray-0.0.6.tgz",
"integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c="
},
"typescript": {
"version": "3.6.4",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.6.4.tgz",
"integrity": "sha512-unoCll1+l+YK4i4F8f22TaNVPRHcD9PA3yCuZ8g5e0qGqlVlJ/8FSateOLLSagn+Yg5+ZwuPkL8LFUc0Jcvksg==",
"dev": true
},
"ua-parser-js": {
"version": "0.7.14",
"resolved": "http://storage.mds.yandex.net/get-npm/66641/ua-parser-js-0.7.14.tgz",
@@ -15785,6 +16128,11 @@
}
}
},
"use-media": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/use-media/-/use-media-1.4.0.tgz",
"integrity": "sha512-XsgyUAf3nhzZmEfhc5MqLHwyaPjs78bgytpVJ/xDl0TF4Bptf3vEpBNBBT/EIKOmsOc8UbuECq3mrP3mt1QANA=="
},
"util": {
"version": "0.10.3",
"resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz",
@@ -16617,6 +16965,12 @@
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/ylru/-/ylru-1.2.1.tgz",
"integrity": "sha512-faQrqNMzcPCHGVC2aaOINk13K+aaBDUPjGWl0teOXywElLjyVAB6Oe2jj62jHYtwsU49jXhScYbvPENK+6zAvQ=="
},
"yn": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
"integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
"dev": true
}
}
}
+13 -2
View File
@@ -6,7 +6,8 @@
"main": "src/bin/www.js",
"scripts": {
"start": "NODE_ENV=production node src/server.js",
"dev": "node src/server.js",
"dev": "ts-node --project server.tsconfig.json src/server.ts",
"next-dev": "next dev",
"build": "next build && next export -o dist && npm run sitemap && npm run feed && npm run robots-txt",
"robots-txt": "node scripts/robots-txt.js",
"sitemap": "node scripts/sitemap.js",
@@ -44,7 +45,8 @@
"redux-thunk": "^2.2.0",
"request-promise": "^2.0.1",
"sitemap": "^1.5.0",
"thunkify": "^2.1.2"
"thunkify": "^2.1.2",
"use-media": "^1.4.0"
},
"devDependencies": {
"@babel/cli": "^7.4.4",
@@ -54,7 +56,14 @@
"@babel/plugin-proposal-object-rest-spread": "^7.4.4",
"@babel/polyfill": "^7.0.0-beta.47",
"@babel/preset-env": "^7.4.5",
"@babel/preset-typescript": "^7.6.0",
"@babel/register": "^7.0.0-beta.47",
"@types/classnames": "^2.2.9",
"@types/koa": "^2.0.50",
"@types/koa-route": "^3.2.4",
"@types/mongodb": "^3.3.6",
"@types/node": "^12.7.12",
"@types/react": "^16.9.5",
"@zeit/next-css": "^1.0.1",
"@zeit/next-sass": "^1.0.1",
"autoprefixer": "^6.3.6",
@@ -73,6 +82,8 @@
"mocha": "^6.1.4",
"node-sass": "^4.12.0",
"should": "^8.2.2",
"ts-node": "^8.4.1",
"typescript": "^3.6.4",
"webpack": "^4.32.2"
}
}
+43
View File
@@ -0,0 +1,43 @@
import React from 'react'
import App, { Container } from 'next/app'
class MyApp extends App {
render() {
const { Component, pageProps } = this.props
return (
<Container>
<style global jsx>{`
@media (max-width: 31.25em) {
:root {
--smaller-font-size: 0.9rem;
--normal-font-size: 1rem;
--bigger-font-size: 1.1rem;
--big-font-size: 1.3rem;
--biggest-font-size: 2rem;
--font-size-h1: 1.75rem;
--font-size-h2: 1.5rem;
--font-size-h3: 1.25rem;
--content-thin: 90%;
--content-wide: 90%;
}
}
.noscroll {
overflow: hidden;
}
#__next {
height: 100%;
}
`}</style>
<Component {...pageProps} />
</Container>
)
}
}
export default MyApp
-2
View File
@@ -63,8 +63,6 @@ export default class MyDocument extends Document {
<body className="custom_class">
<Main />
<NextScript />
<script src="/static/app.js" />
</body>
</Html>
);
+10 -11
View File
@@ -12,21 +12,20 @@ const Page = (props) => (
menu={buildMenu(props.pageUrl, menuModel)}
showAuthor={true}
menuPadding={true}
layout={'thin'}
>
<Head>
<title>{props.album.title}</title>
</Head>
<div className="content content_thin">
<Article
title={props.album.title}
date={props.album.date}
shareable={true}
>
<Html
html={props.album.post}
/>
</Article>
</div>
<Article
title={props.album.title}
date={props.album.date}
shareable={true}
>
<Html
html={props.album.post}
/>
</Article>
</App>
)
@@ -1,10 +1,9 @@
import { connect } from '../../../src/core/db'
import { NextApiRequest, NextApiResponse } from 'next'
import Article from '../../../src/core/Article'
import { encodeArticle } from '../../../src/api/articles'
export default async (req, res) => {
await connect()
import { encodeArticle } from '../../../src/server/articles'
import { withMiddleware } from '../../../src/middlewares/withMiddleware'
export default withMiddleware(async (req: NextApiRequest, res: NextApiResponse) => {
const {
query: { article: id }
} = req;
@@ -19,4 +18,4 @@ export default async (req, res) => {
error: `Article ${id} not found`
})
}
}
})
@@ -1,10 +1,7 @@
import { connect } from '../../../src/core/db'
import { findArticles } from '../../../src/api/articles'
import { findArticles } from '../../../src/server/articles'
import { withMiddleware } from '../../../src/middlewares/withMiddleware'
export default withMiddleware(async (req, res) => {
await connect()
const { query } = req
const page = parseInt(query.page)
const pageSize = parseInt(query.pageSize)
@@ -16,7 +13,7 @@ export default withMiddleware(async (req, res) => {
} else {
res.status(404)
res.json({
error: `Article ${id} not found`
error: `Articles not found`
})
}
})
-3
View File
@@ -1,11 +1,8 @@
import { connect } from '../../src/core/db'
import Image from '../../src/core/Image'
import { encodeImage } from '../../src/api/image'
import { withMiddleware } from '../../src/middlewares/withMiddleware'
export default withMiddleware(async (req, res) => {
await connect()
const file = req.query.file
const image = await Image.findByFile(file)
-3
View File
@@ -1,10 +1,7 @@
import { connect } from '../../../src/core/db'
import Page from '../../../src/core/Page'
import { withMiddleware } from '../../../src/middlewares/withMiddleware'
export default withMiddleware(async (req, res) => {
await connect()
const items = await Page.find({})
if (items) {
+2 -4
View File
@@ -1,13 +1,11 @@
import { connect } from '../../src/core/db'
import { findSchedule } from '../../src/core/schedule'
import { findSchedule } from '../../src/server/schedule'
import { withMiddleware } from '../../src/middlewares/withMiddleware'
export default withMiddleware(async (req, res) => {
await connect()
const period = req.query.period
const semester = req.query.semester
const data = await findSchedule(period, semester)
const data = await findSchedule(req.collection, period, semester)
if (data) {
res.json(data)
+11 -12
View File
@@ -15,23 +15,22 @@ const Page = (props) => (
menu={buildMenu(props.pageUrl, menuModel)}
showAuthor={true}
menuPadding={true}
layout={'thin'}
>
<Head>
<title>{props.article.title}</title>
<Meta meta={props.meta} />
</Head>
<div className={'content content_thin'}>
<Article
title={props.article.title}
date={props.article.date}
tags={props.article.tags}
shareable={true}
>
<Html
html={props.article.post}
/>
</Article>
</div>
<Article
title={props.article.title}
date={props.article.date}
tags={props.article.tags}
shareable={true}
>
<Html
html={props.article.post}
/>
</Article>
</App>
)
+6 -7
View File
@@ -13,18 +13,17 @@ const Page = (props) => (
menu={buildMenu(props.pageUrl, menuModel)}
showAuthor={true}
menuPadding={true}
layout={'wide'}
>
<Head>
<title>{props.title}</title>
<Meta meta={props.meta} />
</Head>
<div className="content content_wide">
<ArticleCardList
articles={props.articles}
prevPage={props.prevPage}
nextPage={props.nextPage}
/>
</div>
<ArticleCardList
articles={props.articles}
prevPage={props.prevPage}
nextPage={props.nextPage}
/>
</App>
)
+22 -23
View File
@@ -4,7 +4,7 @@ import { get } from 'lodash'
import { App } from '../src/components/App'
import { Meta } from '../src/components/Meta'
import { CollectiveImage } from '../src/components/CollectiveImage'
import PersonCard from '../src/components/PersonCard'
import { PersonCard } from '../src/components/PersonCard'
import { CardList } from '../src/components/CardList'
import menuModel from '../src/models/menu'
import { buildMenu } from '../src/lib/menu'
@@ -16,35 +16,34 @@ const Page = (props) => (
menu={buildMenu(props.pageUrl, menuModel)}
showAuthor={true}
menuPadding={true}
layout={'wide'}
>
<Head>
<title>{props.title}</title>
<Meta meta={props.meta} />
</Head>
<div className="content content_semi-wide">
{!props.image ? null : (
<CollectiveImage
data={props.image}
style={{
marginBottom: 'var(--double-margin)',
}}
{!props.image ? null : (
<CollectiveImage
data={props.image}
style={{
marginBottom: 'var(--double-margin)',
}}
/>
)}
<CardList
items={props.persons}
renderItem={(person, index) => (
<PersonCard
key={index}
profile={person}
picture={person.picture}
url={person.url}
name={person.name}
/>
)}
<CardList
items={props.persons}
renderItem={(person, index) => (
<PersonCard
key={index}
profile={person}
picture={person.picture}
url={person.url}
name={person.name}
/>
)}
/>
</div>
/>
</App>
)
@@ -56,7 +55,7 @@ Page.getInitialProps = wrapInitialProps(async (ctx) => {
const title = 'Преподаватели Шлиссельбургской ДХШ'
const imageFile = 'Images/HudozkaCollective2017.jpg'
const resImage = await requestGet(createApiUrl(ctx.req, `/api/image?file=${imageFile}`), null)
const image = get(resImage, 'data.artifacts.large', null)
const image = get(resImage, 'artifacts.large', null)
return {
persons,
+4 -5
View File
@@ -2,7 +2,7 @@ import React from 'react'
import Head from 'next/head'
import { App } from '../../src/components/App'
import Document from '../../src/components/Document'
import Share from '../../src/components/Share'
import { Share } from '../../src/components/Share'
import menuModel from '../../src/models/menu'
import { buildMenu } from '../../src/lib/menu'
import { meta } from '../../src/lib/meta'
@@ -14,15 +14,14 @@ const Page = (props) => (
menu={buildMenu(props.pageUrl, menuModel)}
showAuthor={true}
menuPadding={true}
layout={'wide'}
>
<Head>
<title>{props.file.title}</title>
<Meta meta={props.meta} />
</Head>
<div className="content content_wide">
<Document {...props.file} />
<Share />
</div>
<Document {...props.file} />
<Share />
</App>
)
+38 -22
View File
@@ -5,12 +5,19 @@ import { Meta } from '../src/components/Meta'
import menuModel from '../src/models/menu'
import { buildMenu } from '../src/lib/menu'
import { meta } from '../src/lib/meta'
import { IDocument } from '../src/types'
import DocumentList from '../src/components/DocumentList'
import { DocumentList } from '../src/components/DocumentList'
import { unique } from '../src/utils/common'
import { createApiUrl, requestGet, wrapInitialProps } from '../src/next-lib'
import { createApiUrl, requestGet, wrapInitialProps, IResponseItems } from '../src/next-lib'
import { NextPage } from 'next'
function getSorted(documents) {
interface IDocumentCategory {
name: string
items: IDocument[]
}
function getSorted(documents: IDocument[]): IDocumentCategory[] {
const compose = (...fns) => value => fns
.map(fn => fn)
.reverse()
@@ -23,7 +30,7 @@ function getSorted(documents) {
const subtract = (a, b) => a - b
const uniqueCategories = unique(i => i.category)
const uniqueCategories = unique<IDocument, string>(i => i.category)
const priority = {
'Основные документы': [
@@ -53,13 +60,16 @@ function getSorted(documents) {
'Документы для поступления',
]
const sortBy = fn => (a, b) => [a, b]
.map(fn)
.reduce(subtract)
function sortBy<T>(fn: (value: T) => number): (a: T, b: T) => number {
return (a, b) => [a, b].map(fn).reduce(subtract)
}
const getPriorityFn = (m, list) => value => list.includes(value)
? list.indexOf(value)
: m
function getPriorityFn<T>(m: number, list: T[]): (value: T) => number {
return (value: T) => {
const has = list.includes(value)
return has ? list.indexOf(value) : m
}
}
const safeSelect = (d, store, key) => key in store
? store[key]
@@ -84,33 +94,39 @@ function getSorted(documents) {
}], [])
}
const Page = (props) => (
interface IProps {
collections: IDocumentCategory[]
pageUrl: string
title: string
meta: any
}
const Page: NextPage<IProps> = props => (
<App
menu={buildMenu(props.pageUrl, menuModel)}
showAuthor={true}
menuPadding={true}
layout={'thin'}
>
<Head>
<title>{props.title}</title>
<Meta meta={props.meta} />
</Head>
<div className="content content_thin">
{props.collections.map(({ name, items }, index) => (
<DocumentList
key={index}
name={name}
documents={items}
/>
))}
</div>
{props.collections.map((collection, index) => (
<DocumentList
key={index}
name={collection.name}
documents={collection.items}
/>
))}
</App>
)
Page.getInitialProps = wrapInitialProps(async (ctx) => {
const pageUrl = '/documents'
const res = await requestGet(createApiUrl(ctx.req, '/api/files'), {})
const files = res.items || []
const res = await requestGet<IResponseItems<IDocument>>(createApiUrl(ctx.req, '/api/files'), { items: [] })
const files = res.items
const title = 'Документы'
const collections = getSorted(files)
+23 -20
View File
@@ -1,14 +1,14 @@
import React from 'react'
import Head from 'next/head'
import { App } from '../src/components/App'
// import Album from '../src/components/Album'
import Article from '../src/components/Article'
import { Meta } from '../src/components/Meta'
import menuModel from '../src/models/menu'
import { buildMenu } from '../src/lib/menu'
import { splitBy } from '../src/lib/array'
import { meta } from '../src/lib/meta'
import { createApiUrl, requestGet, wrapInitialProps } from '../src/next-lib'
import { NextPage } from 'next'
import { PageHeader } from '../src/components/PageHeader'
const albumsByYear = splitBy(album => new Date(album.date).getFullYear())
@@ -63,39 +63,42 @@ const ACollection = ({ title, albums }) => (
</div>
)
interface IProps {
meta: any
pageUrl: string
title: string
collections: any[]
}
const Page = (props) => (
const Page: NextPage<IProps> = props => (
<App
menu={buildMenu(props.pageUrl, menuModel)}
showAuthor={true}
menuPadding={true}
layout={'wide'}
>
<Head>
<title>{props.title}</title>
<Meta meta={props.meta} />
</Head>
<div className="content content_semi-wide">
<Article
// title={meta.title}
title={'Галерея'}
>
{props.collections.map(([year, albums], index) => (
<ACollection
key={index}
title={year}
albums={albums}
/>
))
}
</Article>
</div>
<PageHeader
title={props.title}
/>
{props.collections.map(([year, albums], index) => (
<ACollection
key={index}
title={year}
albums={albums}
/>
))}
</App>
)
Page.getInitialProps = wrapInitialProps(async (ctx) => {
const pageUrl = ctx.req.url
const res = await requestGet(createApiUrl(ctx.req, `/api/albums`), {})
const res = await requestGet<any>(createApiUrl(ctx.req, `/api/albums`), {})
const items = res.items || []
const albumCollections = albumsByYear(items)
const title = 'Галерея'
@@ -111,4 +114,4 @@ Page.getInitialProps = wrapInitialProps(async (ctx) => {
}
})
export default Page
export default Page
+19 -10
View File
@@ -8,27 +8,36 @@ import menuModel from '../src/models/menu'
import { meta } from '../src/lib/meta'
import { buildMenu } from '../src/lib/menu'
import { createApiUrl, requestGet, wrapInitialProps } from '../src/next-lib'
import { NextPage } from 'next'
const Page = (props) => (
interface IProps {
pageUrl: string
title: string
meta: any
prevPage: number
nextPage: number
articles: any[]
}
const Page: NextPage<IProps> = props => (
<App
menu={buildMenu(props.pageUrl, menuModel)}
showAuthor={true}
menuPadding={true}
layout={'wide'}
>
<Head>
<title>{props.title}</title>
<Meta meta={props.meta} />
</Head>
<div className="content content_wide">
<HudozkaTitle />
<HudozkaTitle />
<ArticleCardList
articles={props.articles}
prevPage={props.prevPage}
nextPage={props.nextPage}
/>
</div>
<ArticleCardList
articles={props.articles}
prevPage={props.prevPage}
nextPage={props.nextPage}
/>
</App>
)
@@ -36,7 +45,7 @@ Page.getInitialProps = wrapInitialProps(async (ctx) => {
const pageUrl = '/'
const page = 1
const pageSize = ctx.query.pageSize || 15
const res = await requestGet(createApiUrl(ctx.req, `/api/articles?page=${page}&pageSize=${pageSize}`), {})
const res = await requestGet(createApiUrl(ctx.req, `/api/articles?page=${page}&pageSize=${pageSize}`), {}) as any
const articles = res.items || []
const nextPage = res.nextPage
const prevPage = res.prevPage
+10 -11
View File
@@ -1,37 +1,36 @@
import React from 'react'
import Head from 'next/head'
import { App } from '../src/components/App'
import Page from '../src/components/Page'
import { Page } from '../src/components/Page'
import menuModel from '../src/models/menu'
import { buildMenu } from '../src/lib/menu'
import { Meta } from '../src/components/Meta'
import { meta } from '../src/lib/meta'
import { createApiUrl, requestGet, wrapInitialProps } from '../src/next-lib'
const Index = (props) => (
const Index = props => (
<App
menu={buildMenu(props.pageUrl, menuModel)}
showAuthor={true}
menuPadding={true}
layout={'thin'}
>
<Head>
<title>{props.title}</title>
<Meta meta={props.meta} />
</Head>
<div className={'content content_thin'}>
<Page
shareable={true}
>
{props.content}
</Page>
</div>
<Page
showSocialShare={true}
>
{props.content}
</Page>
</App>
)
Index.getInitialProps = wrapInitialProps(async (ctx) => {
const pageUrl = ctx.req.url
// // const pageUrl = '/neighbors/2018'
const page = await requestGet(createApiUrl(ctx.req, `/api/page?page=${pageUrl}`), {})
const image = page.preview ? page.preview.artifacts.fb : {}
@@ -48,4 +47,4 @@ Index.getInitialProps = wrapInitialProps(async (ctx) => {
}
})
export default Index
export default Index
+6 -7
View File
@@ -1,8 +1,8 @@
import React from 'react'
import Head from 'next/head'
import { App } from '../src/components/App'
import Schedule from '../src/components/Schedule'
import Share from '../src/components/Share'
import { Schedule } from '../src/components/Schedule'
import { Share } from '../src/components/Share'
import menuModel from '../src/models/menu'
import { buildMenu } from '../src/lib/menu'
import { createApiUrl, requestGet, wrapInitialProps } from '../src/next-lib'
@@ -12,18 +12,17 @@ const Page = (props) => (
menu={buildMenu(props.pageUrl, menuModel)}
showAuthor={true}
menuPadding={true}
layout={'wide'}
>
<Head>
<title>{props.title}</title>
</Head>
<div className="content content_wide">
{/*<ScheduleList schedules={schedules}/>*/}
{/*<ScheduleList schedules={schedules}/>*/}
<Schedule {...props.schedule} />
<Schedule {...props.schedule} />
<Share />
</div>
<Share />
</App>
)
+5 -7
View File
@@ -1,8 +1,7 @@
import React from 'react'
import axios from 'axios'
import Head from 'next/head'
import { App } from '../../src/components/App'
import Page from '../../src/components/Page'
import { Page } from '../../src/components/Page'
import { Meta } from '../../src/components/Meta'
import menuModel from '../../src/models/menu'
import { buildMenu } from '../../src/lib/menu'
@@ -14,16 +13,15 @@ const Index = (props) => (
menu={buildMenu(props.pageUrl, menuModel)}
showAuthor={true}
menuPadding={true}
layout={'wide'}
>
<Head>
<title>{props.title}</title>
<Meta meta={props.meta} />
</Head>
<div className="content content_thin">
<Page shareable={true}>
{props.person.post}
</Page>
</div>
<Page shareable={true}>
{props.person.post}
</Page>
</App>
)
+14
View File
@@ -0,0 +1,14 @@
{
"compilerOptions": {
"noEmitOnError": false,
"target": "es5",
"module": "commonjs",
"jsx": "preserve",
"outDir": "./build",
"strict": false,
"esModuleInterop": true
},
"include": [
"src/**/*.ts"
]
}
-32
View File
@@ -1,32 +0,0 @@
import React from 'react'
import Menu from './Menu'
import Footer from './Footer'
import Comments from './Comments'
import '../style/style.scss'
export const App = ({children, menu, showAuthor, menuPadding}) => (
<div className="body-wrapper theme-default">
<header>
<div className="navigation">
<div className="navigation__body">
<Menu items={menu.items}/>
</div>
</div>
</header>
<main className="body-wrapper__content">
<section className={`content content_full ${menuPadding ? 'content--padding-top--menu' : ''}`}>
{children}
</section>
<Comments/>
</main>
<Footer showAuthor={showAuthor}
address=" г. Шлиссельбург ул. 18 января д. 3"
telephone="+7 (81362) 76-312"
email="hudozka@gmail.com"
/>
</div>
)
+8
View File
@@ -0,0 +1,8 @@
import * as React from 'react'
import { Navigation } from '../Navigation'
const Header: React.FC<any> = props => (
<Navigation items={props.menuItems} />
)
export default Header
+56
View File
@@ -0,0 +1,56 @@
import * as React from 'react'
import dynamic from 'next/dynamic'
import cx from 'classnames'
import { Footer } from '../Footer'
import Comments from '../Comments'
import { useReducedMotion } from '../../hooks/useReducedMotion'
const Header = dynamic(() => import('./Header'), {
ssr: false,
})
import '../../style/style.scss'
export interface IAppProps {
menu: any
showAuthor: boolean
menuPadding: boolean
layout: 'full' | 'wide' | 'thin'
}
export const App: React.FC<IAppProps> = props => {
const motionDisabled = useReducedMotion()
return (
<div className="body-wrapper theme-default">
<header>
<div className="navigation">
<div className="navigation__body">
<Header
menuItems={props.menu.items}
/>
</div>
</div>
</header>
<main className="body-wrapper__content">
<section className={cx('content', `content_${props.layout}`, {
'content--padding-top--menu': props.menuPadding,
'reduced-motion': motionDisabled,
})}>
{props.children}
</section>
<Comments />
</main>
<Footer
showAuthor={props.showAuthor}
address=" г. Шлиссельбург ул. 18 января д. 3"
telephone="+7 (81362) 76-312"
email="hudozka@gmail.com"
/>
</div>
)
}
+1 -9
View File
@@ -1,5 +1,6 @@
import React from 'react'
import { dateFormat } from '../lib/date'
import { Share } from './Share'
const Tag = ({ children }) => (
<li className="ArticleTags-Item">
@@ -29,15 +30,6 @@ const Head = ({ date, tags = [], children }) => (
</header>
)
const Share = () => (
<div className="likely">
<div className="vkontakte">Поделиться</div>
<div className="facebook">Поделиться</div>
<div className="telegram">Отправить</div>
<div className="twitter">Твитнуть</div>
</div>
)
const Article = ({ children, date, tags, title, shareable }) => (
<article className="Article">
<Head
+14
View File
@@ -0,0 +1,14 @@
import * as React from 'react'
import { dateFormat } from '../../lib/date'
export const Date: React.FC<{ children: string }> = ({ children }) => (
<p>
<style jsx>{`
p {
color: #ccc;
font-size: small;
}
`}</style>
{dateFormat(children)}
</p>
)
@@ -1,13 +1,20 @@
import React from 'react'
import { Image } from './Image'
import { dateFormat } from '../lib/date'
import * as React from 'react'
import cx from 'classnames'
import { Date } from './Date'
const Date = ({ children }) => (
<p className="date">{dateFormat(children)}</p>
)
interface IImage {
alt: string
src: string
srcSet: Array<{ url: string, density: number }>
}
export const ArticleCard = ({ article }) => (
<article className="ArticleCard">
export interface IArticleCardProps {
article: any
preview?: IImage
}
export const ArticleCard: React.FC<IArticleCardProps> = ({ article, ...props }) => (
<section className="ArticleCard">
<style jsx>{`
.ArticleCard {
--article-card-width: 30%;
@@ -19,8 +26,8 @@ export const ArticleCard = ({ article }) => (
margin: var(--article-card-margin);
width: var(--article-card-width);
min-width: 10em;
max-width: 30em;
min-width: 15em;
max-width: 20em;
border-radius: 10px;
@@ -29,8 +36,8 @@ export const ArticleCard = ({ article }) => (
}
.ArticleCard > a {
display: block;
position: relative;
display: block;
position: relative;
}
.ArticleCard-image {
@@ -64,11 +71,6 @@ export const ArticleCard = ({ article }) => (
padding: .75em 1em;
}
.ArticleCard .date {
color: #ccc;
font-size: small;
}
.ArticleCard:hover img {
width: 105%;
height: 105%;
@@ -78,20 +80,14 @@ export const ArticleCard = ({ article }) => (
}
// Tablet
@media screen and (min-width: var(--tablet-min-width)) and (max-width: var(--tablet-max-width)) {
.ArticleCard {
--article-card-image-height: 12em;
}
@media screen and (orientation: portrait) {
}
}
// Mobile
@media (max-width: var(--mobile-width)) {
@media (max-width: 31.25em) {
.ArticleCard {
--article-card-width: 100%;
--article-card-margin: var(--single-margin) 0;
@@ -101,12 +97,15 @@ export const ArticleCard = ({ article }) => (
<a className="invisible" href={article.url}>
<div className="ArticleCard-image">
{!article.preview ? null : (
<Image
data={article.preview}
alt={article.title}
opa={false}
/>
{!props.preview ? null : (
<picture>
<img
className={cx({ opa: false })}
alt={props.preview.alt}
src={props.preview.src}
srcSet={props.preview.srcSet.map(({ url, density }) => `${url} ${density}x`).join(' ')}
/>
</picture >
)}
</div>
@@ -114,9 +113,11 @@ export const ArticleCard = ({ article }) => (
{article.title}
{!article.date ? null : (
<Date>{article.date}</Date>
<Date>
{article.date}
</Date>
)}
</div>
</a>
</article>
</section>
)
+26 -8
View File
@@ -3,18 +3,36 @@ import Paginator from './Paginator'
import { ArticleCard } from './ArticleCard'
export const ArticleCardList = ({ articles, nextPage, prevPage }) => (
<div className="ArticleCardList">
<div>
<style jsx>{`
.body {
display: flex;
flex-direction: row;
justify-content: center;
flex-wrap: wrap;
}
`}</style>
{!prevPage ? null : (
<Paginator url={`/articles/${prevPage}`} type="top" />
)}
<div className="ArticleCardList-body">
{articles.map((article, i) => (
<ArticleCard
key={i}
article={article}
/>
))}
<div className={'body'}>
{articles.map((article, i) => {
const preview = {
alt: article.title,
src: article.preview.src,
srcSet: article.preview.set,
}
return (
<ArticleCard
key={i}
article={article}
preview={preview}
/>
)
})}
</div>
{!nextPage ? null : (
+12 -15
View File
@@ -1,25 +1,22 @@
import React from 'react'
import Image from './Image'
import { Image } from './Image'
export const CollectiveImage = (props) => (
<div
className="CollectiveImage"
style={props.style}
>
<style jsx>{`
.CollectiveImage {
margin - bottom: var(--double-margin);
}
img {
width: 100%;
border-radius: 10px;
}
`}</style>
<Image
data={props.data}
src={props.data.src}
alt={props.data.alt}
set={props.data.set}
opa={false}
style={{
marginBottom: 'var(--double-margin)',
}}
imgStyle={{
width: '100%',
borderRadius: '10px',
}}
/>
</div>
</div>
)
-18
View File
@@ -1,18 +0,0 @@
const React = require('react')
const Document = require('./DocumentListItem')
const DocumentList = ({documents, name}) => (
<div className="documents-container">
<div className="documents-container__head">
<h2>{name}</h2>
</div>
<div className="documents-container__body">
{documents.map((d, i) => (
<Document key={i} {...d}/>
))}
</div>
</div>
)
module.exports = DocumentList
+22
View File
@@ -0,0 +1,22 @@
import * as React from 'react'
import { IDocument } from '../../types'
import { DocumentListItem } from '../DocumentListItem'
export interface IDocumentListProps {
documents: IDocument[]
name: string
}
export const DocumentList: React.FC<IDocumentListProps> = props => (
<div className="documents-container">
<div className="documents-container__head">
<h2>{props.name}</h2>
</div>
<div className="documents-container__body">
{props.documents.map((d, i) => (
<DocumentListItem key={i} {...d} />
))}
</div>
</div>
)
-23
View File
@@ -1,23 +0,0 @@
const React = require('react')
const {ext, size} = require('../lib/file')
const {titleCase} = require('../lib/string')
const Document = ({title, url, imageUrl, fileUrl, fileName, fileSize}) => (
<div className="document-row">
<a href={url} className="invisible">
<div className="document-row__image">
<img src={imageUrl} alt={title}/>
</div>
</a>
<div className="document-row__file">
<a href={url}>{titleCase(title)}</a>
</div>
<div className="document-row__file-info">
<a href={fileUrl} target="_blank">{ext(fileName)} ({size(fileSize)})</a>
</div>
</div>
)
module.exports = Document
+30
View File
@@ -0,0 +1,30 @@
import * as React from 'react'
import { ext, size } from '../../lib/file'
import { titleCase } from '../../lib/string'
export interface IDocumentListItemProps {
title: string
url: string
imageUrl: string
fileUrl: string
fileName: string
fileSize: number
}
export const DocumentListItem: React.FC<IDocumentListItemProps> = props => (
<div className="document-row">
<a href={props.url} className="invisible">
<div className="document-row__image">
<img src={props.imageUrl} alt={props.title} />
</div>
</a>
<div className="document-row__file">
<a href={props.url}>{titleCase(props.title)}</a>
</div>
<div className="document-row__file-info">
<a href={props.fileUrl} target="_blank">{ext(props.fileName)} ({size(props.fileSize)})</a>
</div>
</div>
)
-44
View File
@@ -1,44 +0,0 @@
const React = require('react')
const mailto = email => `mailto:${email}`
const currentYear = () => (new Date()).getFullYear()
const Footer = ({children, address, telephone, email, showAuthor}) => (
<footer>
<div className="contacts">
<div>
<h4>Контактная информация</h4>
<p>Адрес: {address}</p>
<p>Телефон: {telephone}</p>
<p>Почта: <a href={mailto(email)}>{email}</a></p>
<p>Вконтакте: <a href="https://vk.com/shlisselburghudozka">shlisselburghudozka</a></p>
<p>Инстаграм: <a href="https://www.instagram.com/hudozka/">hudozka</a></p>
</div>
</div>
<div className="copyright">
<Copyright
yearStart={2012}
yearEnd={currentYear()}
/>
{!showAuthor ? null : (
<Author/>
)}
</div>
</footer>
)
const Author = () => (
<span>
Разработка и поддержка сайта —
<a href="http://tmshv.ru?utm_source=artshburg&utm_medium=developer&utm_campaign=tmshv_ru">
www.tmshv.ru
</a>
</span>
)
const Copyright = ({yearStart, yearEnd}) => (
<span>©&nbsp;{yearStart}—{yearEnd} Шлиссельбургская детская художественная школа</span>
)
module.exports = Footer
+49
View File
@@ -0,0 +1,49 @@
import * as React from 'react'
const mailto = email => `mailto:${email}`
const currentYear = () => (new Date()).getFullYear()
export interface IFooterProps {
address: string
telephone: string
email: string
showAuthor: boolean
}
export const Footer: React.FC<IFooterProps> = props => (
<footer>
<div className="contacts">
<div>
<h4>Контактная информация</h4>
<p>Адрес: {props.address}</p>
<p>Телефон: {props.telephone}</p>
<p>Почта: <a href={mailto(props.email)}>{props.email}</a></p>
<p>Вконтакте: <a href="https://vk.com/shlisselburghudozka">shlisselburghudozka</a></p>
<p>Инстаграм: <a href="https://www.instagram.com/hudozka/">hudozka</a></p>
</div>
</div>
<div className="copyright">
<Copyright
yearStart={2012}
yearEnd={currentYear()}
/>
{!props.showAuthor ? null : (
<Author />
)}
</div>
</footer>
)
const Author = () => (
<span>
Разработка и поддержка сайта —
<a href="http://tmshv.ru?utm_source=artshburg&utm_medium=developer&utm_campaign=tmshv_ru">
www.tmshv.ru
</a>
</span>
)
const Copyright = ({ yearStart, yearEnd }) => (
<span>©&nbsp;{yearStart}—{yearEnd} Шлиссельбургская детская художественная школа</span>
)
-8
View File
@@ -1,8 +0,0 @@
import React from 'react'
export const HudozkaTitle = () => (
<div className="HudozkaTitle">
<p>МБУДО</p>
<h1>Шлиссельбургская детская художественная школа</h1>
</div>
)
+33
View File
@@ -0,0 +1,33 @@
import * as React from 'react'
export const HudozkaTitle: React.FC = React.memo(() => (
<div>
<style jsx>{`
div {
--padding: var(--double-margin) 0;
padding: var(--padding);
text-align: center;
}
p {
color: var(--text-color-second);
margin-top: var(--single-margin);
font-size: var(--font-size-second);
}
h1 {
margin-top: var(--single-margin);
}
@media (max-width: 31.25em) {
div {
--padding: 0 0 var(--double-margin);
}
}
`}</style>
<p>МБУДО</p>
<h1>Шлиссельбургская <br /> детская художественная школа</h1>
</div>
))
-21
View File
@@ -1,21 +0,0 @@
import React from 'react'
import className from 'classnames'
export const Image = ({data, alt, opa = true}) => (
<picture>
<style jsx>{`
picture img {
display: block;
}
`}</style>
<img
className={className({ opa })}
alt={alt}
src={data.src}
srcSet={data.set.map(({url, density}) => `${url} ${density}x`)}
/>
</picture >
)
export default Image
+40
View File
@@ -0,0 +1,40 @@
import * as React from 'react'
import cx from 'classnames'
export interface IImageProps {
style?: React.CSSProperties
imgStyle?: React.CSSProperties
src: string
alt: string
opa: boolean
set: Array<{
url: string
density: number
}>
}
export const Image: React.FC<IImageProps> = React.memo(props => {
const srcSet = props.set
.map(({ url, density }) => `${url} ${density}x`)
.join(' ')
return (
<picture style={props.style}>
<style jsx>{`
picture img {
display: block;
}
`}</style>
<img
style={props.imgStyle}
className={cx({
opa: props.opa
})}
alt={props.alt}
src={props.src}
srcSet={srcSet}
/>
</picture >
)
})
-58
View File
@@ -1,58 +0,0 @@
const React = require('react')
const getPathWithNoTrailingSlas = require('../lib/url').getPathWithNoTrailingSlash
const hasSubmenu = item => !!item.items
const doDrawSubmenu = item => false //item.active && hasSubmenu(item)
const itemUrl = url => getPathWithNoTrailingSlas(url)
const selectedClassName = flag => flag
? 'selected'
: ''
const hmenuCurClassName = flag => flag
? 'HMenu__item--cur'
: ''
const MenuItem = ({active, text, url}) => (active
? <span>{text}</span>
: <a href={itemUrl(url)}>{text}</a>
)
const MenuToggle = () => (
<div className="HMenu__toggle">
<a href="#"></a>
</div>
)
const Menu = ({items}) => {
const content = items.map((item, index) => (
<li key={index}
className={`HMenu__item ${hmenuCurClassName(item.highlighted)} ${item.color} ${selectedClassName(item.highlighted)}`}
>
<MenuItem {...item}/>
{!doDrawSubmenu(item) ? null : (
<Submenu key={index} items={item.items}/>
)}
</li>
))
return (
<menu className="main-menu HMenu" data-toggle-width="1024">
<MenuToggle/>
{content}
</menu>
)
}
const Submenu = ({items}) => (
<menu className="main-menu__submenu">
{items.map((item, index) => (
<li key={index} className={`${item.color} ${selectedClassName(item.active)}`}>
<MenuItem {...item}/>
</li>
))}
</menu>
)
module.exports = Menu
+33
View File
@@ -0,0 +1,33 @@
import * as React from 'react'
export interface IMenuItemProps {
active: boolean
text: string
url: string
}
export const MenuItem: React.FC<IMenuItemProps> = ({ active, text, url }) => (
<>
<style jsx>{`
a {
color: var(--dark-color);
}
a:visited {
color: var(--dark-color);
}
a:hover {
border-color: var(--dark-color);
}
`}</style>
{active
? (
<span>{text}</span>
) : (
<a href={url}>{text}</a>
)
}
</>
)
+125
View File
@@ -0,0 +1,125 @@
import * as React from 'react'
import cx from 'classnames'
import { getPathWithNoTrailingSlash } from '../../lib/url'
import { MenuItem } from './MenuItem'
const itemUrl = (url: string) => getPathWithNoTrailingSlash(url)
export interface IMenuProps {
layout: 'desktop' | 'mobile'
items: any[]
}
export const Menu: React.FC<IMenuProps> = props => (
<div className={cx('menu', props.layout)}>
<style jsx>{`
.menu {
display: flex;
align-items: flex-start;
list-style: none;
}
div.desktop {
flex-direction: row;
flex-wrap: wrap;
}
div.mobile {
flex-direction: column;
}
li{
position: relative;
border-top: var(--menu-colored-mark-thickness) solid rgba(0, 0, 0, 0);
}
li.desktop {
padding: 0.3em 0.2em 0;
margin-right: 1em;
font-size: var(--normal-font-size);
}
li.mobile {
padding: 0.3em 0.5em;
margin-bottom: var(--half-margin);
font-size: 1.25em;
color: white;
}
li:last-child {
margin-right: 0;
}
li.selected {
font-weight: bold;
}
li.desktop.selected.blue {
border-top-color: var(--blue-color);
}
li.desktop.selected.orange {
border-top-color: var(--orange-color);
}
li.desktop.selected.green {
border-top-color: var(--green-color);
}
li.desktop.selected.yellow {
border-top-color: var(--yellow-color);
}
li.desktop.selected.pink {
border-top-color: var(--pink-color);
}
li.desktop.selected.red {
border-top-color: var(--red-color);
}
li.mobile.selected.blue {
background-color: var(--blue-color);
}
li.mobile.selected.orange {
background-color: var(--orange-color);
}
li.mobile.selected.green {
background-color: var(--green-color);
}
li.mobile.selected.yellow {
background-color: var(--yellow-color);
}
li.mobile.selected.pink {
background-color: var(--pink-color);
}
li.mobile.selected.red {
background-color: var(--red-color);
}
`}</style>
{props.items.map((item, index) => (
<li key={index}
className={cx(
props.layout,
item.color, {
selected: item.highlighted,
})}
>
<MenuItem
url={itemUrl(item.url)}
text={item.text}
active={item.active}
/>
</li>
))}
</div>
)
+79
View File
@@ -0,0 +1,79 @@
import * as React from 'react'
import cx from 'classnames'
export interface IMenuToggleProps {
style?: React.CSSProperties
open: boolean
position: 'left' | 'right'
onClick: () => void
}
export const MenuToggle: React.FC<IMenuToggleProps> = props => {
return (
<div
className={cx(props.position, {
open: props.open,
close: !props.open,
})}
style={props.style}
>
<style jsx>{`
div {
--hmenu-margin: 6px;
--hmenu-icon-width: 32px;
--hmenu-icon-height: 32px;
position: absolute;
height: var(--hmenu-icon-height);
margin: var(--hmenu-margin);
}
div.right {
top: 0;
right: 0;
}
div.left {
top: 0;
left: 0;
}
a {
display: block;
text-decoration: none;
border: none;
width: var(--hmenu-icon-width);
height: var(--hmenu-icon-height);
background-size: 100%;
}
a:hover {
text-decoration: none;
border: none;
}
div.open a {
background-image: url(/static/graphics/hmenu-close-b.png);
}
div.open a:hover {
background-image: url(/static/graphics/hmenu-close-hover-b.png);
}
div.close a {
background-image: url(/static/graphics/hmenu-b.png);
}
div.close a:hover {
background-image: url(/static/graphics/hmenu-hover-b.png);
}
`}</style>
<a
href="#"
onClick={props.onClick}
></a>
</div>
)
}
@@ -1,7 +1,12 @@
import React from 'react'
import * as React from 'react'
import { IMeta } from '../../types'
export const Meta = ({ meta }) => (
<React.Fragment>
export interface IMetaProps {
meta: IMeta
}
export const Meta: React.FC<IMetaProps> = ({ meta }) => (
<>
{/* Search Engine */}
<meta name="description" content={meta.description} />
<meta name="image" content={meta.image} />
@@ -15,8 +20,8 @@ export const Meta = ({ meta }) => (
<meta property="og:title" content={meta.title} />
<meta property="og:description" content={meta.description} />
<meta property="og:image" content={meta.image} />
<meta property="og:image:width" content={meta.imageWidth} />
<meta property="og:image:height" content={meta.imageHeight} />
<meta property="og:image:width" content={meta.imageWidth.toString()} />
<meta property="og:image:height" content={meta.imageHeight.toString()} />
<meta property="og:url" content={meta.url} />
<meta property="og:site_name" content={meta.siteName} />
<meta property="og:locale" content={meta.locale} />
@@ -31,5 +36,5 @@ export const Meta = ({ meta }) => (
<meta name="twitter:url" content={meta.url} />
<meta name="twitter:site" content={meta.twitterSite} />
<meta name="twitter:creator" content={meta.twitterCreator} />
</React.Fragment>
)
</>
)
+67
View File
@@ -0,0 +1,67 @@
import * as React from 'react'
import { MenuToggle } from '../MenuToggle'
import { useScreenType } from '../../hooks/useScreenType'
import { Menu } from '../Menu'
import { Overlay } from '../Overlay'
export interface INavigationProps {
items: any[]
}
export const Navigation: React.FC<INavigationProps> = props => {
const isMobile = useScreenType(['phone', 'tablet'])
const [open, setOpen] = React.useState(false)
const onClick = React.useCallback(
() => {
setOpen(!open)
},
[open]
)
return (
<nav>
<style jsx>{`
nav {
display: flex;
align-items: flex-start;
margin-left: 1em;
margin-right: 1em;
font-size: var(--normal-font-size);
list-style: none;
}
`}</style>
{isMobile ? (
<MenuToggle
open={open}
position={'right'}
onClick={onClick}
style={{
zIndex: 1000001,
}}
/>
) : (
<Menu
layout={'desktop'}
items={props.items}
/>
)}
<Overlay
show={open}
onClickOverlay={onClick}
>
<div style={{
width: '50%',
margin: '50px auto',
}}>
<Menu
layout={'mobile'}
items={props.items}
/>
</div>
</Overlay>
</nav>
)
}
+49
View File
@@ -0,0 +1,49 @@
import * as React from 'react'
export interface ITransitionProps {
duration: number
extraStyle?: React.CSSProperties
show: boolean
}
export const Transition: React.FC<ITransitionProps> = props => {
const [mounted, setMounted] = React.useState(props.show)
const [opacity, setOpacity] = React.useState(0)
const onTransitionEnd = React.useCallback(() => {
if (opacity === 0) {
setMounted(false)
}
}, [opacity])
const transition = React.useMemo(
() => `all ${props.duration}ms`,
[props.duration])
React.useEffect(() => {
if (!props.show) {
setOpacity(0)
return
}
setMounted(true)
let t = setTimeout(() => {
setOpacity(1)
}, 16)
return () => {
clearTimeout(t)
}
}, [props.show])
return mounted && (
<div
style={{
...props.extraStyle,
opacity,
transition,
}}
onTransitionEnd={onTransitionEnd}
>
{props.children}
</div>
)
}
+51
View File
@@ -0,0 +1,51 @@
import * as React from 'react'
import { Transition } from './Transition'
import { useReducedMotion } from '../../hooks/useReducedMotion'
export interface IOverlayProps {
show: boolean
onClickOverlay: () => void
}
export const Overlay: React.FC<IOverlayProps> = props => {
const motionDisabled = useReducedMotion()
const duration = motionDisabled ? 0 : 250
const onClick = React.useCallback((event: React.MouseEvent<HTMLDivElement>) => {
if (event.target === event.currentTarget) {
props.onClickOverlay()
}
}, [props.onClickOverlay])
React.useEffect(() => {
document.body.classList.toggle('noscroll', props.show)
}, [props.show])
return (
<Transition
duration={duration}
show={props.show}
extraStyle={{
zIndex: 1000000,
}}
>
<div
onClick={onClick}
>
<style jsx>{`
div {
position: fixed;
width: 100%;
height: 100%;
top: 0;
left: 0;
background-color: rgb(255, 255, 255);
}
`}</style>
{props.children}
</div>
</Transition>
)
}
-19
View File
@@ -1,19 +0,0 @@
import React from 'react'
import Share from './Share'
import Html from './Html'
export default function Page({ children, shareable }) {
return (
<div className={'Article Article--cloud'}>
<div className={'Article-Body'}>
<Html
html={children}
/>
</div>
{!shareable ? null : (
<Share />
)}
</div>
)
}
+21
View File
@@ -0,0 +1,21 @@
import * as React from 'react'
import { Share } from '../Share'
import Html from '../Html'
export interface IPageProps {
showSocialShare: boolean
}
export const Page: React.FC<IPageProps> = props => (
<div className={'Article Article--cloud'}>
<div className={'Article-Body'}>
<Html
html={props.children}
/>
</div>
{!props.showSocialShare ? null : (
<Share />
)}
</div>
)
+22
View File
@@ -0,0 +1,22 @@
import * as React from 'react'
import { dateFormat } from '../../lib/date'
export interface IPageHeaderProps {
tags?: string[]
date?: Date
title: string
}
export const PageHeader: React.FC<IPageHeaderProps> = props => (
<header className="Article-Head">
<h1>{props.title}</h1>
{!props.date ? null : (
<p className="date">{dateFormat(props.date)}</p>
)}
{/* {!(tags.length) ? null : (
<TagList tags={tags} />
)} */}
</header>
)
+11 -6
View File
@@ -1,7 +1,8 @@
import React from 'react'
import cx from 'classnames'
import { Image } from './Image'
module.exports = ({ picture, name, url, profile }) => (
export const PersonCard = ({ picture, name, url, profile }) => (
<div className="PersonCard">
<style jsx>{`
.PersonCard {
@@ -84,7 +85,7 @@ module.exports = ({ picture, name, url, profile }) => (
// Mobile
@media (max-width: var(--mobile-width)) {
@media (max-width: 31.25em) {
.PersonCard {
--person-card-width: 100%;
}
@@ -93,10 +94,14 @@ module.exports = ({ picture, name, url, profile }) => (
<a className="invisible" href={url}>
<div className="PersonCard-Picture">
<Image
data={picture}
opa={false}
/>
<picture>
<img
className={cx({ opa: false })}
alt={''}
src={picture.src}
srcSet={picture.set.map(({ url, density }) => `${url} ${density}x`)}
/>
</picture >
</div>
<div className="PersonCard-Title">
-123
View File
@@ -1,123 +0,0 @@
const React = require('react')
const {getDates} = require('../utils/date')
const {shortName} = require('../models/collective')
const now = new Date()
const dates = getDates(now)
const isToday = weekDayIndex => (now.getDay() - 1) === weekDayIndex
const weekDay = weekDayIndex => dates[weekDayIndex]
const short = shortName
const todayClass = i => isToday(i)
? 'current'
: ''
const HeadDay = ({i, children}) => (
<div className={`schedule-table__head-record ${todayClass(i)}`}>
<p className="day">{children}</p>
<p className="date">{weekDay(i)}</p>
</div>
)
const days = [
'Понедельник',
'Вторник',
'Среда',
'Четверг',
'Пятница',
'Суббота',
]
const Schedule = ({groups}) => (
<div className="schedule-table">
<div className="schedule-table__body">
<div className="schedule-table__body-row">
<div className="schedule-table__body-group">Гр.</div>
{days.map((day, index) => (
<HeadDay i={index}>{day}</HeadDay>
))}
</div>
{groups.map((group, index) => (
<GroupRow key={index} {...group}/>
))}
</div>
</div>
)
const GroupRow = ({name, time, week}) => (
<div className="schedule-table__body-row">
<div className="schedule-table__body-group">
<p className="name">{name}</p>
<p className="time">{time}</p>
</div>
{week.map((day, index) => (
<Group key={index} day={day} dayIndex={index}/>
))}
</div>
)
const Group = ({day, dayIndex}) => (
<div className={`schedule-table__record ${todayClass(dayIndex)}`}>
{day.map((lesson, index) => (
<Cell key={index} {...lesson}/>
))}
</div>
)
const Cell = ({course, teacher, time}) => (
<div className="schedule-table__course">
<Lesson {...course}/>
<Teacher {...teacher}/>
{time
.map(range => ({range}))
.map((range, index) => (
<TimeRange key={index} {...range}/>
))}
</div>
)
const Lesson = ({title, url}) => (
<p className="lesson">
{url
? (
<a href={url}>
{title}
</a>
)
: (
<span>
{title}
</span>
)
}
</p>
)
const Teacher = ({name, url}) => (
<p className="teacher">
{url
? (
<a href={url}>
{short(name)}
</a>
)
: (
<span>
{short(name)}
</span>
)
}
</p>
)
const TimeRange = ({range}) => (
<p className="time">{(range || []).join(' — ')}</p>
)
module.exports = Schedule
+126
View File
@@ -0,0 +1,126 @@
import React from 'react'
const { getDates } = require('../utils/date')
const { shortName } = require('../models/collective')
const now = new Date()
const dates = getDates(now)
const isToday = weekDayIndex => (now.getDay() - 1) === weekDayIndex
const weekDay = weekDayIndex => dates[weekDayIndex]
const short = shortName
const todayClass = i => isToday(i)
? 'current'
: ''
const HeadDay = ({ i, children }) => (
<div className={`schedule-table__head-record ${todayClass(i)}`}>
<p className="day">{children}</p>
<p className="date">{weekDay(i)}</p>
</div>
)
const days = [
'Понедельник',
'Вторник',
'Среда',
'Четверг',
'Пятница',
'Суббота',
]
export const Schedule = ({ groups }) => (
<div className="schedule-table">
<div className="schedule-table__body">
<div className="schedule-table__body-row">
<div className="schedule-table__body-group">Гр.</div>
{days.map((day, index) => (
<HeadDay
key={index}
i={index}
>
{day}
</HeadDay>
))}
</div>
{groups.map((group, index) => (
<GroupRow key={index} {...group} />
))}
</div>
</div>
)
const GroupRow = ({ name, time, week }) => (
<div className="schedule-table__body-row">
<div className="schedule-table__body-group">
<p className="name">{name}</p>
<p className="time">{time}</p>
</div>
{week.map((day, index) => (
<Group key={index} day={day} dayIndex={index} />
))}
</div>
)
const Group = ({ day, dayIndex }) => (
<div className={`schedule-table__record ${todayClass(dayIndex)}`}>
{day.map((lesson, index) => (
<Cell key={index} {...lesson} />
))}
</div>
)
const Cell = ({ course, teacher, time }) => (
<div className="schedule-table__course">
<Lesson {...course} />
<Teacher {...teacher} />
{time
.map(range => ({ range }))
.map((range, index) => (
<TimeRange key={index} {...range} />
))}
</div>
)
const Lesson = ({ title, url }) => (
<p className="lesson">
{url
? (
<a href={url}>
{title}
</a>
)
: (
<span>
{title}
</span>
)
}
</p>
)
const Teacher = ({ name, url }) => (
<p className="teacher">
{url
? (
<a href={url}>
{short(name)}
</a>
)
: (
<span>
{short(name)}
</span>
)
}
</p>
)
const TimeRange = ({ range }) => (
<p className="time">{(range || []).join(' — ')}</p>
)
+1 -1
View File
@@ -1,5 +1,5 @@
const React = require('react')
const text = require('../core/schedule').text
const text = require('../server/schedule').text
const ScheduleList = ({schedules}) => (
<div className="schedule-selector">
@@ -1,6 +1,6 @@
const React = require('react')
import * as React from 'react'
const Share = () => (
export const Share: React.FC = () => (
<div className="likely">
<div className="vkontakte">Поделиться</div>
<div className="facebook">Поделиться</div>
@@ -8,5 +8,3 @@ const Share = () => (
<div className="twitter">Твитнуть</div>
</div>
)
module.exports = Share
-29
View File
@@ -1,29 +0,0 @@
const React = require('react')
const Share = require('./Share')
const getHtml = require('../lib/component').getHtml
const Head = ({url, children}) => (
!url
? <h1>{children}</h1>
: <a href={url}><h2>{children}</h2></a>
)
const TeacherProfile = ({picture, name, url, profile, biography, shareable}) => (
<div className="teacher-profile">
<div className="teacher-profile__picture">
<img src={picture.url}/>
</div>
<div className="teacher-profile__info">
<div className="teacher-profile__biography">
{getHtml(biography)}
</div>
</div>
{!shareable ? null : (
<Share/>
)}
</div>
)
module.exports = TeacherProfile
-65
View File
@@ -1,65 +0,0 @@
const Data = require('./Data')
const Image = require('./Image')
const ImageArtifactType = require('./ImageArtifactType')
const {find, findOne} = require('../lib/store')
const previewTypes = [
ImageArtifactType.SMALL,
ImageArtifactType.MEDIUM,
ImageArtifactType.BIG,
ImageArtifactType.ORIGIN,
]
const store = () => Data.getStore(Album)
class Album {
static async find(query, options = {}) {
const items = await find(store(), query, options)
return Promise.all(items.map(processAlbum))
}
static async findById(id) {
const item = await findOne(store(), {id})
return !item
? null
: processAlbum(item)
}
}
const previewFromImage = (imgs = []) => imgs.length
? imgs[0]
: null
const processAlbum = async album => {
let preview = {}
// {
// data: [
// {
// url: null,
// width: 0,
// height: 0,
// }
// ]
// }
const previewImageId = album.preview ? album.preview : previewFromImage(album.images)
if (previewImageId) {
const image = await Image.findById(previewImageId)
if (image) {
// const imageArtifacts = image.findArtifact(previewTypes)
// if (imageArtifacts) {
// preview = imageArtifacts
// }
preview = image.getPicture(ImageArtifactType.SMALL)
}
}
return {
...album,
preview,
url: `/album/${album.id}`,
}
}
module.exports = Album
+63
View File
@@ -0,0 +1,63 @@
import Data from './Data'
import Image from './Image'
import ImageArtifactType from './ImageArtifactType'
import { find, findOne } from '../lib/store'
const previewTypes = [
ImageArtifactType.SMALL,
ImageArtifactType.MEDIUM,
ImageArtifactType.BIG,
ImageArtifactType.ORIGIN,
]
const store = () => Data.getStore('Album')
export default class Album {
static async find(query, options = {}) {
const items = await find(store(), query, options as any)
return Promise.all(items.map(processAlbum))
}
static async findById(id) {
const item = await findOne(store(), { id })
return !item
? null
: processAlbum(item)
}
}
const previewFromImage = (imgs = []) => imgs.length
? imgs[0]
: null
const processAlbum = async album => {
let preview = {}
// {
// data: [
// {
// url: null,
// width: 0,
// height: 0,
// }
// ]
// }
const previewImageId = album.preview ? album.preview : previewFromImage(album.images)
if (previewImageId) {
const image = await Image.findById(previewImageId)
if (image) {
// const imageArtifacts = image.findArtifact(previewTypes)
// if (imageArtifacts) {
// preview = imageArtifacts
// }
preview = image.getPicture(ImageArtifactType.SMALL)
}
}
return {
...album,
preview,
url: `/album/${album.id}`,
}
}
-117
View File
@@ -1,117 +0,0 @@
const Data = require('./Data')
const Image = require('./Image')
const Config = require('./Config')
const {find, findOne, total} = require('../lib/store')
const store = () => Data.getStore(Article)
function make(C) {
return x => new C(x)
}
class Article {
static async find(query, options = {}) {
const items = await find(store(), query, options)
return Promise.all(items.map(processArticle))
}
static async findById(id) {
const item = await findOne(store(), {id})
return !item
? null
: processArticle(item)
}
static async total(query = {}) {
return total(store(), query)
}
static async findPinned() {
const now = new Date()
const query = {until: {$gte: now}}
return Article.find(query, {
sort: {date: -1},
})
}
constructor(data) {
this.id = data.id
this.images = data.images
this.post = data.post
this.folder = data.folder
this.date = data.date
this.title = data.title
this.until = data.until
this.tags = (data.tags || []).map(make(Tag))
this.content = data.content
this.hash = data.hash
this.type = data.type
this.origin = data.origin
this.file = data.file
this.version = data.version
this.preview = data.preview
this.url = `/article/${this.id}`
}
plain() {
return {
id: this.id,
images: this.images,
post: this.post,
folder: this.folder,
date: this.date,
title: this.title,
until: this.until,
tags: this.tags,
content: this.content,
hash: this.hash,
type: this.type,
origin: this.origin,
file: this.file,
version: this.version,
preview: this.preview,
url: this.url,
}
}
}
class Tag {
constructor(tag) {
this.name = tag;
}
}
const previewFromImage = (imgs = []) => imgs.length
? imgs[0]
: null
async function resolveDefaultPreview() {
const config = await Config.findConfig()
if (!config) return null
return Image.findByFile(config.articleCardDefaultPreview)
}
async function processArticle(article) {
let preview = null
const previewId = article.preview
? article.preview
: previewFromImage(article.images)
if (previewId) {
preview = await Image.findById(previewId)
} else {
preview = await resolveDefaultPreview()
}
return new Article({
...article,
preview,
})
}
module.exports = Article
+134
View File
@@ -0,0 +1,134 @@
import Data from './Data'
import Image from './Image'
import Config from './Config'
import { find, findOne, total } from '../lib/store'
const store = () => Data.getStore<Article>('Article')
function make(C) {
return x => new C(x)
}
export default class Article {
static async find(query, options = {}): Promise<Article[]> {
const items = await find(store(), query, options as any)
return Promise.all(items.map(processArticle))
}
static async findById(id) {
const item = await findOne(store(), { id })
return !item
? null
: processArticle(item)
}
static async total(query = {}) {
return total(store(), query)
}
static async findPinned() {
const now = new Date()
const query = { until: { $gte: now } }
return Article.find(query, {
sort: { date: -1 },
})
}
public id: any
public images: any
public post: any
public folder: any
public date: any
public title: any
public until: any
public tags: any
public content: any
public hash: any
public type: any
public origin: any
public file: any
public version: any
public preview: any
public url: any
constructor(data) {
this.id = data.id
this.images = data.images
this.post = data.post
this.folder = data.folder
this.date = data.date
this.title = data.title
this.until = data.until
this.tags = (data.tags || []).map(make(Tag))
this.content = data.content
this.hash = data.hash
this.type = data.type
this.origin = data.origin
this.file = data.file
this.version = data.version
this.preview = data.preview
this.url = `/article/${this.id}`
}
plain() {
return {
id: this.id,
images: this.images,
post: this.post,
folder: this.folder,
date: this.date,
title: this.title,
until: this.until,
tags: this.tags,
content: this.content,
hash: this.hash,
type: this.type,
origin: this.origin,
file: this.file,
version: this.version,
preview: this.preview,
url: this.url,
}
}
}
class Tag {
public name: string
constructor(tag) {
this.name = tag;
}
}
const previewFromImage = (imgs = []) => imgs.length
? imgs[0]
: null
async function resolveDefaultPreview() {
const config = await Config.findConfig()
if (!config) return null
return Image.findByFile(config.articleCardDefaultPreview)
}
async function processArticle(article) {
let preview = null
const previewId = article.preview
? article.preview
: previewFromImage(article.images)
if (previewId) {
preview = await Image.findById(previewId)
} else {
preview = await resolveDefaultPreview()
}
return new Article({
...article,
preview,
})
}
-37
View File
@@ -1,37 +0,0 @@
const Data = require('./Data')
const {findOne} = require('../lib/store')
class Config {
static get store() {
return Data.getStore(Config)
}
static async findConfig() {
const config = await Config.findById('settings')
return config || new Config({})
}
static async findById(id) {
const data = await findOne(Config.store, {id})
if (!data) return null
return new Config(data)
}
constructor({hash, file, ...data}) {
this.hash = hash
this.file = file
this.server = data.server
this.articlesPerPage = data.articlesPerPage || 5
this.collectiveOrder = data.collectiveOrder || []
this.articleCardDefaultPreview = data.articleCardDefaultPreview
this.collectiveImage = data.collectiveImage
const redirect = data.redirect || {}
this.redirect = new Map(Object
.entries(redirect)
)
}
}
module.exports = Config
+42
View File
@@ -0,0 +1,42 @@
import Data from './Data'
import { findOne } from '../lib/store'
const store = () => Data.getStore('Settings')
export default class Config {
static async findConfig() {
const config = await Config.findById('settings')
return config || new Config({} as any)
}
static async findById(id: string) {
const data = await findOne(store(), { id })
if (!data) return null
return new Config(data)
}
public hash: any
public file: any
public server: any
public articlesPerPage: any
public collectiveOrder: any
public articleCardDefaultPreview: any
public collectiveImage: any
public redirect: any
constructor({ hash, file, ...data }) {
this.hash = hash
this.file = file
this.server = data.server
this.articlesPerPage = data.articlesPerPage || 5
this.collectiveOrder = data.collectiveOrder || []
this.articleCardDefaultPreview = data.articleCardDefaultPreview
this.collectiveImage = data.collectiveImage
const redirect = data.redirect || {}
this.redirect = new Map(Object
.entries(redirect)
)
}
}
-19
View File
@@ -1,19 +0,0 @@
const db = require('./db')
const store = new Map()
class Data {
static setStore(Class, collection) {
store.set(Class, collection)
}
static getStore(Class) {
return store.get(Class)
}
static getCollection(name) {
return db.collection(name)
}
}
module.exports = Data
+18
View File
@@ -0,0 +1,18 @@
import { Collection } from 'mongodb'
import * as db from './db'
const store = new Map<string, Collection>()
export default class Data {
static setStore(key: string, collection: Collection) {
store.set(key, collection)
}
static getStore<T>(key: string): Collection<T> {
return store.get(key)
}
static getCollection(name: string): Collection {
return db.collection(name)
}
}
+20 -8
View File
@@ -1,12 +1,12 @@
const Data = require('./Data')
const Image = require('./Image')
const { find, findOne } = require('../lib/store')
import Data from './Data'
import Image from './Image'
import { find, findOne } from '../lib/store'
const store = () => Data.getStore(Document)
const store = () => Data.getStore('Document')
class Document {
export default class Document {
static async find(query, options = {}) {
const items = await find(store(), query, options)
const items = await find(store(), query, options as any)
return Promise.all(items.map(processDocument))
}
@@ -18,6 +18,17 @@ class Document {
return processDocument(data)
}
public id: any
public hash: any
public file: any
public fileInfo: any
public type: any
public title: any
public category: any
public url: any
public preview: any
public viewUrl: any
constructor(data) {
this.id = data.id
this.hash = data.hash
@@ -37,6 +48,9 @@ class Document {
}
class DocumentFile {
public name: any
public size: any
constructor(data) {
this.name = data.name
this.size = data.size
@@ -52,5 +66,3 @@ const processDocument = async data => {
preview,
})
}
module.exports = Document
-75
View File
@@ -1,75 +0,0 @@
const Data = require('./Data')
const ImageArtifactType = require('./ImageArtifactType')
const {findOne} = require('../lib/store')
const { mapOf } = require('../server/lib')
const store = () => Data.getStore(Image)
class Image {
static async findById(id) {
const data = await findOne(store(), {_id: id})
if (!data) return null
const artifacts = data.data
return new Image({...data, artifacts})
}
static async findByFile(file) {
const data = await findOne(store(), {file})
if (!data) return null
const artifacts = data.data
return new Image({...data, artifacts})
}
constructor({hash, file, artifacts}) {
this.hash = hash
this.file = file
this.artifacts = mapOf(ImageArtifact, artifacts)
}
getArtifact(size) {
return this.artifacts.get(size)
}
findArtifact(sizes) {
for (let size of sizes) {
const a = this.getArtifact(size)
if (a) return a
}
return null
}
getPicture(size) {
const retinaSize = ImageArtifactType.retina(size)
const a = this.getArtifact(size)
const a2 = this.getArtifact(retinaSize)
const srcset = !a2
? []
: [{
url: a2.url,
density: ImageArtifactType.RETINA_DENSITY,
}]
return {
src: a.url,
width: a.width,
height: a.height,
set: srcset,
}
}
}
class ImageArtifact {
constructor({url, size, width, height}) {
this.url = url
this.size = size
this.width = width
this.height = height
}
}
module.exports = Image
+81
View File
@@ -0,0 +1,81 @@
import Data from './Data'
import ImageArtifactType from './ImageArtifactType'
import { findOne } from '../lib/store'
import { mapOf } from '../server/lib'
const store = () => Data.getStore('Image')
export default class Image {
static async findById(id) {
const data = await findOne(store(), { _id: id })
if (!data) return null
const artifacts = data.data
return new Image({ ...data, artifacts })
}
static async findByFile(file: string) {
const data = await findOne(store(), { file })
if (!data) return null
const artifacts = data.data
return new Image({ ...data, artifacts })
}
public hash: any
public file: any
public artifacts: any
constructor({ hash, file, artifacts }) {
this.hash = hash
this.file = file
this.artifacts = mapOf(ImageArtifact, artifacts)
}
getArtifact(size) {
return this.artifacts.get(size)
}
findArtifact(sizes) {
for (let size of sizes) {
const a = this.getArtifact(size)
if (a) return a
}
return null
}
getPicture(size) {
const retinaSize = ImageArtifactType.retina(size)
const a = this.getArtifact(size)
const a2 = this.getArtifact(retinaSize)
const srcset = !a2
? []
: [{
url: a2.url,
density: ImageArtifactType.RETINA_DENSITY,
}]
return {
src: a.url,
width: a.width,
height: a.height,
set: srcset,
}
}
}
class ImageArtifact {
public url: any
public size: any
public width: any
public height: any
constructor({ url, size, width, height }) {
this.url = url
this.size = size
this.width = width
this.height = height
}
}
-93
View File
@@ -1,93 +0,0 @@
const Data = require('./Data')
const Image = require('./Image')
const Config = require('./Config')
const {find, findOne, total} = require('../lib/store')
const store = () => Data.getStore(Page)
class Page {
static async find(query, options = {}) {
const items = await find(store(), query, options)
return Promise.all(items.map(processPage))
}
static async findById(id) {
const item = await findOne(store(), {id})
return !item
? null
: processPage(item)
}
static async findByUrl(url) {
const item = await findOne(store(), {url})
return !item
? null
: processPage(item)
}
static async total(query = {}) {
return total(store(), query)
}
constructor(data) {
this.id = data.id
this.hash = data.hash
this.file = data.file
this.url = data.url
this.content = data.content
this.title = data.title
this.data = data.data
this.images = data.images
this.documents = data.documents
this.preview = data.preview
}
plain() {
return {
id: this.id,
images: this.images,
documents: this.documents,
data: this.data,
title: this.title,
content: this.content,
hash: this.hash,
file: this.file,
preview: this.preview,
url: this.url,
}
}
}
const previewFromImage = (imgs = []) => imgs.length
? imgs[0]
: null
async function resolveDefaultPreview() {
const config = await Config.findConfig()
if (!config) return null
return Image.findByFile(config.articleCardDefaultPreview)
}
async function processPage(article) {
let preview = null
const previewId = article.preview
? article.preview
: previewFromImage(article.images)
if (previewId) {
preview = await Image.findById(previewId)
} else {
preview = await resolveDefaultPreview()
}
return new Page({
...article,
preview,
})
}
module.exports = Page
+102
View File
@@ -0,0 +1,102 @@
import Data from './Data'
import { find as storeFind, findOne, total } from '../lib/store'
import Image from './Image'
import Config from './Config'
const store = () => Data.getStore('Page')
export default class Page {
static async find(query, options = {}): Promise<Page[]> {
const items = await storeFind(store(), query, options as any)
return Promise.all(items.map(processPage))
}
static async findById(id) {
const item = await findOne(store(), { id })
return !item
? null
: processPage(item)
}
static async findByUrl(url) {
const item = await findOne(store(), { url })
return !item
? null
: processPage(item)
}
static async total(query = {}) {
return total(store(), query)
}
public id: any
public hash: any
public file: any
public url: string
public content: any
public title: any
public data: any
public images: any
public documents: any
public preview: any
constructor(data) {
this.id = data.id
this.hash = data.hash
this.file = data.file
this.url = data.url
this.content = data.content
this.title = data.title
this.data = data.data
this.images = data.images
this.documents = data.documents
this.preview = data.preview
}
plain() {
return {
id: this.id,
images: this.images,
documents: this.documents,
data: this.data,
title: this.title,
content: this.content,
hash: this.hash,
file: this.file,
preview: this.preview,
url: this.url,
}
}
}
const previewFromImage = (imgs = []) => imgs.length
? imgs[0]
: null
async function resolveDefaultPreview() {
const config = await Config.findConfig()
if (!config) return null
return Image.findByFile(config.articleCardDefaultPreview)
}
async function processPage(article) {
let preview = null
const previewId = article.preview
? article.preview
: previewFromImage(article.images)
if (previewId) {
preview = await Image.findById(previewId)
} else {
preview = await resolveDefaultPreview()
}
return new Page({
...article,
preview,
})
}
-76
View File
@@ -1,76 +0,0 @@
const Data = require('./Data')
const Image = require('./Image')
const {find, findOne} = require('../lib/store')
const store = () => Data.getStore(Teacher)
class Teacher {
static async find(query, options = {}) {
const items = await find(store(), query, options)
return Promise.all(items.map(processProfile))
}
static async findById(id) {
const data = await findOne(store(), {id})
if (!data) return null
return processProfile(data)
}
constructor(data) {
this.id = data.id
this.post = data.post
this.file = data.file
this.edu = data.edu
this.picture = data.picture
this.preview = data.preview
this.diploma = data.diploma
this.position = data.position
this.name = data.name
this.status = data.status
this.hash = data.hash
this.url = `/teacher/${this.id}`
}
/**
*
* "First Middle Last" -> "F. M. Last"
*
* @returns {string}
*/
shortName() {
const f = i => i.replace(/^(.).+/, '$1')
const [first, middle, last] = this.splitName()
return `${f(first)}. ${f(middle)}. ${last}`
}
/**
*
* "First Middle Last" -> ["First", "Middle", "Last"]
*
* @returns {Array|{index: number, input: string}}
*/
splitName() {
const name = this.name
const m = /(.+)\s(.+)\s(.+)/.exec(name)
return m.slice(1)
}
}
const processProfile = async profile => {
const imageId = profile.picture
const previewId = profile.preview
const picture = await Image.findById(imageId)
const preview = await Image.findById(previewId)
return new Teacher({
...profile,
picture,
preview,
})
}
module.exports = Teacher
+87
View File
@@ -0,0 +1,87 @@
import Data from './Data'
import Image from './Image'
import { find, findOne } from '../lib/store'
const store = () => Data.getStore('Teacher')
export default class Teacher {
static async find(query, options = {}) {
const items = await find(store(), query, options as any)
return Promise.all(items.map(processProfile))
}
static async findById(id) {
const data = await findOne(store(), { id })
if (!data) return null
return processProfile(data)
}
public id: any
public post: any
public file: any
public edu: any
public picture: any
public preview: any
public diploma: any
public position: any
public name: any
public status: any
public hash: any
public url: any
constructor(data) {
this.id = data.id
this.post = data.post
this.file = data.file
this.edu = data.edu
this.picture = data.picture
this.preview = data.preview
this.diploma = data.diploma
this.position = data.position
this.name = data.name
this.status = data.status
this.hash = data.hash
this.url = `/teacher/${this.id}`
}
/**
*
* "First Middle Last" -> "F. M. Last"
*
* @returns {string}
*/
shortName() {
const f = i => i.replace(/^(.).+/, '$1')
const [first, middle, last] = this.splitName()
return `${f(first)}. ${f(middle)}. ${last}`
}
/**
*
* "First Middle Last" -> ["First", "Middle", "Last"]
*
* @returns {Array|{index: number, input: string}}
*/
splitName() {
const name = this.name
const m = /(.+)\s(.+)\s(.+)/.exec(name)
return m.slice(1)
}
}
const processProfile = async profile => {
const imageId = profile.picture
const previewId = profile.preview
const picture = await Image.findById(imageId)
const preview = await Image.findById(previewId)
return new Teacher({
...profile,
picture,
preview,
})
}
-17
View File
@@ -1,17 +0,0 @@
import {c} from '../core/db';
export async function getCollective(query={}) {
return await c('collective')
.find(query)
.toArray();
}
/**
* Find a person by it's id
*
* @param id
* @returns {Promise|*}
*/
export async function getPerson(id){
return await c('collective').findOne({id: id});
}
-52
View File
@@ -1,52 +0,0 @@
const Data = require('./Data')
const Page = require('./Page')
const Article = require('./Article')
const Album = require('./Album')
const Teacher = require('./Teacher')
const Document = require('./Document')
const Image = require('./Image')
const Settings = require('./Config')
const getConfig = require('next/config').default
const { MongoClient, ObjectId } = require('mongodb')
var db
async function connect() {
if (db) {
return db
}
const mongoUri = getConfig().serverRuntimeConfig.dbUri
const connection = await MongoClient.connect(mongoUri)
db = init(connection)
return db
}
function collection(name) {
return db.collection(name)
}
function id(i) {
if (typeof i === 'string') return ObjectId(i)
else if (i instanceof ObjectId) return i
else if (i instanceof Object) return '_id' in i ? ObjectId(i._id) : null
return null
}
function init(db) {
// Data.setStore('Page', db.collection('pages'))
Data.setStore(Page, db.collection('pages'))
Data.setStore(Article, db.collection('articles'))
Data.setStore(Album, db.collection('albums'))
Data.setStore(Image, db.collection('images'))
Data.setStore(Teacher, db.collection('collective'))
Data.setStore(Document, db.collection('documents'))
Data.setStore(Settings, db.collection('settings'))
return db
}
exports.connect = connect
exports.collection = collection
exports.c = collection
exports.id = id
+48
View File
@@ -0,0 +1,48 @@
import Data from './Data'
import Page from './Page'
import Article from './Article'
const Album = require('./Album')
const Teacher = require('./Teacher')
const Document = require('./Document')
const Image = require('./Image')
import Settings from './Config'
const getConfig = require('next/config').default
const { MongoClient, ObjectId } = require('mongodb')
var db
export async function connect() {
if (db) {
return db
}
const mongoUri = getConfig().serverRuntimeConfig.dbUri
const connection = await MongoClient.connect(mongoUri)
db = init(connection)
return db
}
export function collection(name) {
return db.collection(name)
}
export function id(i) {
if (typeof i === 'string') return ObjectId(i)
else if (i instanceof ObjectId) return i
else if (i instanceof Object) return '_id' in i ? ObjectId(i._id) : null
return null
}
function init(db) {
console.log('INIT DB')
Data.setStore('Page', db.collection('pages'))
Data.setStore('Article', db.collection('articles'))
Data.setStore('Album', db.collection('albums'))
Data.setStore('Image', db.collection('images'))
Data.setStore('Teacher', db.collection('collective'))
Data.setStore('Document', db.collection('documents'))
Data.setStore('Settings', db.collection('settings'))
return db
}
+5
View File
@@ -0,0 +1,5 @@
import useMedia from 'use-media'
export function useReducedMotion(): boolean {
return useMedia('(prefers-reduced-motion: reduce)')
}
+15
View File
@@ -0,0 +1,15 @@
import * as React from 'react'
import useMedia from 'use-media'
export type Type = 'phone' | 'tablet' | 'laptop' | 'desktop'
export function useScreenType(types: Type[]): boolean {
const isPhone = useMedia('(max-width: 31.25em)')
const [match, setMatch] = React.useState(false)
React.useEffect(() => {
setMatch(isPhone && types.includes('phone'))
}, [isPhone])
return match
}
-15
View File
@@ -1,15 +0,0 @@
import React from 'react'
import {renderToStaticMarkup} from 'react-dom/server'
import App from '../components/App'
function renderApp({component, ...options}) {
const app = (
<App {...options}>
{component}
</App>
)
return renderToStaticMarkup(app)
}
exports.renderApp = renderApp
-18
View File
@@ -1,18 +0,0 @@
const moment = require('moment')
moment.locale('ru')
/**
*
* Date -> DD.MM.YYYY
*
* @param date
*/
exports.dateFormat = date => moment(date).format('L')
/**
*
* Date -> Unix time
*
* @param date
*/
exports.timestamp = date => date.getTime()
+18
View File
@@ -0,0 +1,18 @@
import moment from 'moment'
moment.locale('ru')
/**
*
* Date -> DD.MM.YYYY
*
* @param date
*/
export const dateFormat = (date: string | Date) => moment(date).format('L')
/**
*
* Date -> Unix time
*
* @param date
*/
export const timestamp = (date: Date) => date.getTime()
-27
View File
@@ -1,27 +0,0 @@
function ext(filname) {
let getExt = name => /\.(\w+)$/.exec(name)[1]
let defaultExt = ext => ext.toUpperCase()
const exts = ['png', 'pdf', 'jpg', 'jpeg', 'gif', 'doc']
const extd = ['ПНГ', 'ПДФ', 'ЖПГ', 'ЖПГ', 'ГИФ', 'ДОК']
let ext = getExt(filname)
const i = exts.indexOf(ext)
return i > -1
? extd[i]
: defaultExt(ext)
}
function size(bytes, precision = 1) {
if (isNaN(parseFloat(bytes)) || !isFinite(bytes)) return ''
const units = ['байт', 'КБ', 'МБ', 'ГБ', 'ТБ', 'ПБ']
const number = Math.floor(Math.log(bytes) / Math.log(1024))
const size = (bytes / Math.pow(1024, Math.floor(number))).toFixed(precision)
return `${size} ${units[number]}`
}
exports.ext = ext
exports.size = size
+23
View File
@@ -0,0 +1,23 @@
const getExt = (name: string) => /\.(\w+)$/.exec(name)[1]
const defaultExt = (ext: string) => ext.toUpperCase()
const exts = ['png', 'pdf', 'jpg', 'jpeg', 'gif', 'doc']
const extd = ['ПНГ', 'ПДФ', 'ЖПГ', 'ЖПГ', 'ГИФ', 'ДОК']
const units = ['байт', 'КБ', 'МБ', 'ГБ', 'ТБ', 'ПБ']
export function ext(filname: string): string {
const ext = getExt(filname)
const i = exts.indexOf(ext)
return i > -1
? extd[i]
: defaultExt(ext)
}
export function size(bytes: any, precision = 1): string {
if (isNaN(parseFloat(bytes)) || !isFinite(bytes)) return ''
const number = Math.floor(Math.log(bytes) / Math.log(1024))
const size = (bytes / Math.pow(1024, Math.floor(number))).toFixed(precision)
return `${size} ${units[number]}`
}
+3 -7
View File
@@ -1,4 +1,4 @@
async function find(collection, query, {sort, skip, limit}) {
export async function find(collection, query, {sort, skip, limit}) {
const cursor = collection.find(query)
if (sort) cursor.sort(sort)
@@ -8,16 +8,12 @@ async function find(collection, query, {sort, skip, limit}) {
return cursor.toArray()
}
async function findOne(collection, query) {
export async function findOne(collection, query) {
return collection.findOne(query)
}
async function total(collection, query) {
export async function total(collection, query) {
return collection
.find(query)
.count()
}
exports.find = find
exports.findOne = findOne
exports.total = total
+20
View File
@@ -0,0 +1,20 @@
// import { MongoClient } from 'mongodb';
// const { connect, collection } = require('../../src/core/db')
import {collection, connect} from '../core/db'
// const client = new MongoClient(process.env.MONGODB_URI, { useNewUrlParser: true });
export const withCollection = handler => async (req, res) => {
await connect()
req.collection = collection
// if (!client.isConnected()) {
// return client.connect().then(() => {
// req.db = client.db('nextjsmongodbapp');
// return handler(req, res);
// });
// }
// req.db = client.db('nextjsmongodbapp');
return handler(req, res)
}
+3 -3
View File
@@ -1,3 +1,3 @@
export const withMiddleware = handler => async (req, res) => {
return handler(req, res);
};
import { withCollection } from './withCollection'
export const withMiddleware = handler => withCollection(handler)
-73
View File
@@ -1,73 +0,0 @@
export default class TimelinePostInstagram {
constructor(date, image, url, username, userpic, text) {
this.type = 'instagram';
this.date = date;
this.image = image;
this.url = url;
this.username = username;
this.userpic = userpic;
this.text = text;
}
}
export function fromInstagram1(post) {
//var author_id = personByInstagram(team.team, post.data.author);
//var name = author_id ? team.short(author_id) : post.data.author;
const name = post.data.author || 'hudozka';
const userpic = 'https://igcdn-photos-a-a.akamaihd.net/hphotos-ak-xaf1/t51.2885-19/11357496_464997000344800_2124591831_a.jpg';
return new TimelinePostInstagram(
post.date,
post.data.image.standard_resolution.url,
post.data.url,
name,
userpic,
post.body
);
}
export function fromInstagram2(post) {
const caption = safe(post => post.data.caption.text, '');
const username = safe(post => post.data.user['username'], '');
const userpic = safe(post => post.data.user['profile_picture'], '');
const image = safe(post => post.data.images['standard_resolution'].url, '');
return new TimelinePostInstagram(
post.date,
image(post),
post.data.link,
username(post),
userpic(post),
caption(post)
);
}
export const types = {
'instagram-1': fromInstagram1,
'instagram-2': fromInstagram2
};
export function createInstance(post) {
if(post.type in types) {
let fn = types[post.type];
try{
return fn(post);
}catch(e){
return null;
}
}
return null;
}
function safe(fn, defaultValue=null){
return function(){
try{
let i = fn.apply(undefined, arguments);
return i ? i : defaultValue;
}catch(e){
return defaultValue
}
}
}
+1 -3
View File
@@ -38,9 +38,7 @@ export function getPeriod(i){
* @param i
* @returns {*}
*/
function getPeriodString(i) {
export function getPeriodString(i) {
return getPeriod(i)
.join('-')
}
exports.getPeriodString = getPeriodString
+7 -3
View File
@@ -1,8 +1,12 @@
import axios from 'axios'
export async function requestGet(url, defaultResponse) {
export interface IResponseItems<T> {
items: T[]
}
export async function requestGet<T>(url: string, defaultResponse: T): Promise<T> {
try {
const res = await axios.get(url)
const res = await axios.get<T>(url)
return res.data
} catch (e) {
@@ -19,7 +23,7 @@ export async function requestGet(url, defaultResponse) {
export function wrapInitialProps(fn) {
return async ctx => {
if (process.env.NODE_ENV === 'production' && process.browser) {
return __NEXT_DATA__.props.pageProps;
return window['__NEXT_DATA__'].props.pageProps;
}
return fn(ctx)
+6 -7
View File
@@ -1,8 +1,8 @@
const Koa = require('koa')
const next = require('next')
const { get } = require('koa-route')
const { connect } = require('./core/db')
const Page = require('./core/Page')
import Koa from 'koa'
import next from 'next'
import { get } from 'koa-route'
import { connect } from './core/db'
import Page from './core/Page'
const dev = process.env.NODE_ENV !== 'production'
const app = next({ dev })
@@ -29,7 +29,7 @@ app.prepare().then(async () => {
server.use(get('*', async ctx => {
const url = ctx.req.url
if (pagesUrls.includes(url)) {
await app.render(ctx.req, ctx.res, '/next', ctx.query)
} else {
@@ -43,4 +43,3 @@ app.prepare().then(async () => {
console.log(`> Ready on http://localhost:${port}`)
})
})
@@ -2,7 +2,7 @@ import Article from '../core/Article'
import ImageArtifactType from '../core/ImageArtifactType'
import { sortBy } from '../utils/sort'
import { timestamp } from '../lib/date'
import { encodeImage } from './image'
import { encodeImage } from '../api/image'
export const sortArticleByDate = sortBy(
x => timestamp(new Date(x.date))
@@ -20,7 +20,7 @@ export async function findArticlesNin(nin, skip, limit, sort) {
})
}
export async function findArticles(page, pageSize) {
export async function findArticles(page: number, pageSize: number) {
const limit = pageSize
const skip = (page - 1) * pageSize
+18
View File
@@ -0,0 +1,18 @@
const { collection } = require('../core/db')
// import { collection } from '../core/db'
export async function getCollective(query = {}) {
return await collection('collective')
.find(query)
.toArray();
}
/**
* Find a person by it's id
*
* @param id
* @returns {Promise|*}
*/
export async function getPerson(id) {
return await collection('collective').findOne({ id: id });
}
+1 -1
View File
@@ -5,7 +5,7 @@
* @param Class
* @param object
*/
exports.mapOf = function mapOf(Class, object) {
export function mapOf(Class, object) {
return Object
.entries(object)
.reduce((map, i) => (
@@ -1,4 +1,5 @@
import {c} from '../core/db'
// import {collection} from '../core/db'
// const {collection} = require('../core/db')
import {createMap} from '../utils/common'
import {getCollective} from './collective'
@@ -10,8 +11,8 @@ const sortBy = require('../lib/sort').sortBy
const SCHEDULES = 'schedules'
async function findSchedule(period, semester) {
const schedule = await c(SCHEDULES).findOne({
export async function findSchedule(collection, period, semester) {
const schedule = await collection(SCHEDULES).findOne({
$or: [
{period: period},
{period: schedulePeriod(period)}
@@ -23,7 +24,7 @@ async function findSchedule(period, semester) {
}
async function findAllScheduleParams() {
const schedules = await c(SCHEDULES)
const schedules = await collection(SCHEDULES)
.find({}, {
period: 1,
semester: 1,
@@ -142,7 +143,7 @@ async function migrate20to30(schedule) {
})
}
exports.findSchedule = findSchedule
exports.findAllScheduleParams = findAllScheduleParams
exports.findDefaultScheduleParams = findDefaultScheduleParams
exports.text = text
// exports.findSchedule = findSchedule
// exports.findAllScheduleParams = findAllScheduleParams
// exports.findDefaultScheduleParams = findDefaultScheduleParams
// exports.text = text
@@ -1,5 +0,0 @@
.ArticleCardList-body {
display: flex;
flex-direction: row;
flex-wrap: wrap;
}
-22
View File
@@ -1,22 +0,0 @@
.HudozkaTitle {
--padding: var(--double-margin) 0;
padding: var(--padding);
text-align: center;
p {
color: var(--text-color-second);
margin-top: var(--single-margin);
font-size: var(--font-size-second);
}
h1 {
margin-top: var(--single-margin);
}
}
@media (max-width: var(--mobile-width)) {
.HudozkaTitle {
--padding: 0 0 var(--double-margin);
}
}
@@ -1,32 +0,0 @@
.accessibility-control {
width: 100%;
padding: var(--single-margin);
span {
margin-right: var(--single-margin);
}
span.active {
font-weight: bold;
}
&__block {
margin-bottom: var(--half-margin);
}
ul li {
display: inline;
margin-right: var(--single-margin);
}
ul {
display: inline;
}
}
.accessibility .accessibility-control {
a {
border-bottom-style: dashed;
}
}
-78
View File
@@ -1,78 +0,0 @@
.main-menu {
font-size: var(--normal-font-size);
list-style: none;
.main-menu__submenu {
background-color: rgba(255, 255, 255, 0.95);
display: block;
position: absolute;
top: 100%;
left: -1em;
padding: 0 0.5em 0.5em;
& > li {
display: block;
padding: 0.3em 0.2em 0.5em;
margin: 0 0.5em;
}
}
& > li {
display: inline-block;
padding: 0.3em 0.2em 0;
margin: 0 0.5em;
position: relative;
&:first-child {
margin-left: 1em;
}
}
a {
color: var(--dark-color);
border-color: var(--dark-color);
&:hover {
border-color: var(--dark-color);
}
}
.selected {
font-weight: bold;
border-top: var(--menu-colored-mark-thickness) solid var(--light-color);
&.blue {
border-top: var(--menu-colored-mark-thickness) solid var(--blue-color);
}
&.orange {
border-top: var(--menu-colored-mark-thickness) solid var(--orange-color);
}
&.green {
border-top: var(--menu-colored-mark-thickness) solid var(--green-color);
}
&.yellow {
border-top: var(--menu-colored-mark-thickness) solid var(--yellow-color);
}
&.pink {
border-top: var(--menu-colored-mark-thickness) solid var(--pink-color);
}
&.red {
border-top: var(--menu-colored-mark-thickness) solid var(--red-color);
}
}
}
@media (max-width: var(--mobile-width)) {
.main-menu{
font-size: 1em;
}
}
+65 -84
View File
@@ -1,109 +1,90 @@
.content {
margin: 0 auto 1em;
margin: 0 auto 1em;
&.content_full{
width: var(--content-full);
}
&.content_full {
width: var(--content-full);
}
&.content_wide{
width: var(--content-wide);
}
&.content_wide {
width: var(--content-wide);
}
&.content_semi-wide{
width: var(--content-semi-wide);
}
&.content_semi-wide {
width: var(--content-semi-wide);
}
&.content_thin{
width: var(--content-thin);
}
&.content_thin {
width: var(--content-thin);
}
&.content--padding-top--menu{
padding-top: var(--menu-thickness);
}
&.content--padding-top--menu {
padding-top: var(--menu-thickness);
}
}
// Animation on load
@keyframes smallSlideInFromTop {
0% {
transform: translateY(-10px);
opacity: 0;
}
100% {
transform: translateY(0);
opacity: 1;
}
0% {
transform: translateY(-10px);
opacity: 0;
}
100% {
transform: translateY(0);
opacity: 1;
}
}
.content {
animation: smallSlideInFromTop 350ms ease-out;
animation: smallSlideInFromTop 350ms ease-out;
}
@media screen and (max-width: var(--mobile-width)) {
--content-mobile: 95%;
.content {
&.content_wide{
width: var(--content-mobile);
}
&.content_semi-wide{
width: var(--content-mobile);
}
&.content_thin{
width: var(--content-mobile);
}
&.content--padding-top--menu{
padding-top: var(--single-margin);
}
}
.content.reduced-motion {
animation: none;
}
@media screen and (min-width: var(--tablet-min-width)) and (max-width: var(--tablet-max-width)) {
$thin-value: 70%;
$wide-value: 95%;
$semi-wide-value: 80%;
.content {
overflow-x: scroll;
&.content_wide {
width: $wide-value;
}
&.content_semi-wide {
width: $semi-wide-value;
}
&.content_thin {
width: $thin-value;
}
&.content--padding-top--menu {
padding-top: var(--double-margin);
}
}
@media screen and (orientation: portrait) {
$wide-value: 100%;
$thin-value: 80%;
$semi-wide-value: 95%;
$thin-value: 70%;
$wide-value: 95%;
$semi-wide-value: 80%;
.content {
&.content_wide {
width: $wide-value;
}
overflow-x: scroll;
&.content_semi-wide {
width: $semi-wide-value;
}
&.content_wide {
width: $wide-value;
}
&.content_thin {
width: $thin-value;
}
&.content_semi-wide {
width: $semi-wide-value;
}
&.content_thin {
width: $thin-value;
}
&.content--padding-top--menu {
padding-top: var(--double-margin);
}
}
@media screen and (orientation: portrait) {
$wide-value: 100%;
$thin-value: 80%;
$semi-wide-value: 95%;
.content {
&.content_wide {
width: $wide-value;
}
&.content_semi-wide {
width: $semi-wide-value;
}
&.content_thin {
width: $thin-value;
}
}
}
}
}
-186
View File
@@ -1,186 +0,0 @@
.no-scroll {
overflow: hidden;
}
.HMenu {
--hmenu-margin: 6px;
--hmenu-icon-width: 32px;
--hmenu-icon-height: 32px;
display: flex;
flex-direction: row;
align-items: center;
width: 100%;
//height: 100%;
position: relative;
list-style: none;
}
.HMenu a {
&:visited {
color: inherit;
}
}
.HMenu.HMenu--folded {
height: 100%;
}
.HMenu--folded.HMenu--closed {
li {
margin: 0;
}
}
.HMenu--folded.HMenu--open {
li {
margin: 0 0 var(--single-margin);
}
}
.HMenu--folded.HMenu--closed {
justify-content: center;
}
.HMenu--folded.HMenu--closed .HMenu__item{
display: none;
}
.HMenu--folded .HMenu__item.HMenu__item--cur{
display: block;
}
.HMenu__toggle {
position: absolute;
left: 0;
height: var(--hmenu-icon-height);
margin: var(--hmenu-margin);
display: flex;
a {
text-decoration: none;
border: none;
width: var(--hmenu-icon-width);
height: var(--hmenu-icon-height);
background-size: 100%;
}
a:hover {
text-decoration: none;
border: none;
}
}
.HMenu--folded.HMenu--closed {
.HMenu__toggle {
a {
background-image: url(/graphics/hmenu-b.png);
}
a:hover {
background-image: url(/graphics/hmenu-hover-b.png);
}
}
}
.HMenu--folded.HMenu--open {
.HMenu__toggle {
a {
background-image: url(/graphics/hmenu-close-b.png);
}
a:hover {
background-image: url(/graphics/hmenu-close-hover-b.png);
}
}
}
.HMenu--unfolded {
margin-left: 1em;
li {
margin: 0 1em .5em .5em;
}
.HMenu__toggle {
display: none;
}
}
.HMenu--folded.HMenu--open {
z-index: 100;
display: flex;
align-items: center;
width: 100%;
height: 100%;
position: fixed;
top: 0;
left: 0;
background-color: #ffffff;
flex-direction: column;
text-align: center;
overflow-y: auto;
font-size: 1.5rem;
/*transition: opacity 0.5s ease-in-out;*/
/*-webkit-transition: opacity 0.5s ease-in-out;*/
}
._HMenu--folded li:last-child {
margin-bottom: 2em;
}
@media (max-width: var(--mobile-width)) {
.HMenu--folded .HMenu__item {
max-width: 10em;
}
.HMenu--folded.HMenu--closed {
.HMenu__toggle {
a {
background-image: url(/graphics/hmenu-b.png);
}
a:hover {
background-image: url(/graphics/hmenu-b.png);
}
}
}
.HMenu--folded.HMenu--open {
.HMenu__toggle {
a {
background-image: url(/graphics/hmenu-close-b.png);
}
a:hover {
background-image: url(/graphics/hmenu-close-b.png);
}
}
}
}
Loaded 100 of 123 files, more files were not shown because too many files have changed in this diff. Show more