postmerge
This commit is contained in:
commit
9c263b697e
182
package-lock.json
generated
182
package-lock.json
generated
|
@ -97,6 +97,7 @@
|
||||||
"husky": "8.0.3",
|
"husky": "8.0.3",
|
||||||
"hygen": "6.2.11",
|
"hygen": "6.2.11",
|
||||||
"i18next-http-backend": "2.2.0",
|
"i18next-http-backend": "2.2.0",
|
||||||
|
"javascript-time-ago": "2.5.9",
|
||||||
"jest": "29.7.0",
|
"jest": "29.7.0",
|
||||||
"js-cookie": "3.0.5",
|
"js-cookie": "3.0.5",
|
||||||
"lint-staged": "14.0.1",
|
"lint-staged": "14.0.1",
|
||||||
|
@ -3808,9 +3809,9 @@
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/@jridgewell/trace-mapping": {
|
"node_modules/@jridgewell/trace-mapping": {
|
||||||
"version": "0.3.19",
|
"version": "0.3.20",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.19.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz",
|
||||||
"integrity": "sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw==",
|
"integrity": "sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jridgewell/resolve-uri": "^3.1.0",
|
"@jridgewell/resolve-uri": "^3.1.0",
|
||||||
|
@ -4494,14 +4495,14 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@peculiar/asn1-schema": {
|
"node_modules/@peculiar/asn1-schema": {
|
||||||
"version": "2.3.6",
|
"version": "2.3.8",
|
||||||
"resolved": "https://registry.npmjs.org/@peculiar/asn1-schema/-/asn1-schema-2.3.6.tgz",
|
"resolved": "https://registry.npmjs.org/@peculiar/asn1-schema/-/asn1-schema-2.3.8.tgz",
|
||||||
"integrity": "sha512-izNRxPoaeJeg/AyH8hER6s+H7p4itk+03QCa4sbxI3lNdseQYCuxzgsuNK8bTXChtLTjpJz6NmXKA73qLa3rCA==",
|
"integrity": "sha512-ULB1XqHKx1WBU/tTFIA+uARuRoBVZ4pNdOA878RDrRbBfBGcSzi5HBkdScC6ZbHn8z7L8gmKCgPC1LHRrP46tA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"asn1js": "^3.0.5",
|
"asn1js": "^3.0.5",
|
||||||
"pvtsutils": "^1.3.2",
|
"pvtsutils": "^1.3.5",
|
||||||
"tslib": "^2.4.0"
|
"tslib": "^2.6.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@peculiar/json-schema": {
|
"node_modules/@peculiar/json-schema": {
|
||||||
|
@ -5332,9 +5333,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/babel__core": {
|
"node_modules/@types/babel__core": {
|
||||||
"version": "7.20.2",
|
"version": "7.20.3",
|
||||||
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.3.tgz",
|
||||||
"integrity": "sha512-pNpr1T1xLUc2l3xJKuPtsEky3ybxN3m4fJkknfIpTCTfIZCDW57oAg+EfCgIIp2rvCe0Wn++/FfodDS4YXxBwA==",
|
"integrity": "sha512-54fjTSeSHwfan8AyHWrKbfBWiEUrNTZsUwPTDSNaaP1QDQIZbeNUg3a59E9D+375MzUw/x1vx2/0F5LBz+AeYA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/parser": "^7.20.7",
|
"@babel/parser": "^7.20.7",
|
||||||
|
@ -5345,18 +5346,18 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/babel__generator": {
|
"node_modules/@types/babel__generator": {
|
||||||
"version": "7.6.5",
|
"version": "7.6.6",
|
||||||
"resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.5.tgz",
|
"resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.6.tgz",
|
||||||
"integrity": "sha512-h9yIuWbJKdOPLJTbmSpPzkF67e659PbQDba7ifWm5BJ8xTv+sDmS7rFmywkWOvXedGTivCdeGSIIX8WLcRTz8w==",
|
"integrity": "sha512-66BXMKb/sUWbMdBNdMvajU7i/44RkrA3z/Yt1c7R5xejt8qh84iU54yUWCtm0QwGJlDcf/gg4zd/x4mpLAlb/w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/types": "^7.0.0"
|
"@babel/types": "^7.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/babel__template": {
|
"node_modules/@types/babel__template": {
|
||||||
"version": "7.4.2",
|
"version": "7.4.3",
|
||||||
"resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.2.tgz",
|
"resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.3.tgz",
|
||||||
"integrity": "sha512-/AVzPICMhMOMYoSx9MoKpGDKdBRsIXMNByh1PXSZoa+v6ZoLa8xxtsT/uLQ/NJm0XVAWl/BvId4MlDeXJaeIZQ==",
|
"integrity": "sha512-ciwyCLeuRfxboZ4isgdNZi/tkt06m8Tw6uGbBSBgWrnnZGNXiEyM27xc/PjXGQLqlZ6ylbgHMnm7ccF9tCkOeQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/parser": "^7.1.0",
|
"@babel/parser": "^7.1.0",
|
||||||
|
@ -5364,18 +5365,18 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/babel__traverse": {
|
"node_modules/@types/babel__traverse": {
|
||||||
"version": "7.20.2",
|
"version": "7.20.3",
|
||||||
"resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.3.tgz",
|
||||||
"integrity": "sha512-ojlGK1Hsfce93J0+kn3H5R73elidKUaZonirN33GSmgTUMpzI/MIFfSpF3haANe3G1bEBS9/9/QEqwTzwqFsKw==",
|
"integrity": "sha512-Lsh766rGEFbaxMIDH7Qa+Yha8cMVI3qAK6CHt3OR0YfxOIn5Z54iHiyDRycHrBqeIiqGa20Kpsv1cavfBKkRSw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/types": "^7.20.7"
|
"@babel/types": "^7.20.7"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/eslint": {
|
"node_modules/@types/eslint": {
|
||||||
"version": "8.44.4",
|
"version": "8.44.6",
|
||||||
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.44.4.tgz",
|
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.44.6.tgz",
|
||||||
"integrity": "sha512-lOzjyfY/D9QR4hY9oblZ76B90MYTB3RrQ4z2vBIJKj9ROCRqdkYl2gSUx1x1a4IWPjKJZLL4Aw1Zfay7eMnmnA==",
|
"integrity": "sha512-P6bY56TVmX8y9J87jHNgQh43h6VVU+6H7oN7hgvivV81K2XY8qJZ5vqPy/HdUoVIelii2kChYVzQanlswPWVFw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/estree": "*",
|
"@types/estree": "*",
|
||||||
|
@ -5383,39 +5384,39 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/estree": {
|
"node_modules/@types/estree": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.3.tgz",
|
||||||
"integrity": "sha512-VeiPZ9MMwXjO32/Xu7+OwflfmeoRwkE/qzndw42gGtgJwZopBnzy2gD//NN1+go1mADzkDcqf/KnFRSjTJ8xJA==",
|
"integrity": "sha512-CS2rOaoQ/eAgAfcTfq6amKG7bsN+EMcgGY4FAFQdvSj2y1ixvOZTUA9mOtCai7E1SYu283XNw7urKK30nP3wkQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/@types/graceful-fs": {
|
"node_modules/@types/graceful-fs": {
|
||||||
"version": "4.1.7",
|
"version": "4.1.8",
|
||||||
"resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.7.tgz",
|
"resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.8.tgz",
|
||||||
"integrity": "sha512-MhzcwU8aUygZroVwL2jeYk6JisJrPl/oov/gsgGCue9mkgl9wjGbzReYQClxiUgFDnib9FuHqTndccKeZKxTRw==",
|
"integrity": "sha512-NhRH7YzWq8WiNKVavKPBmtLYZHxNY19Hh+az28O/phfp68CF45pMFud+ZzJ8ewnxnC5smIdF3dqFeiSUQ5I+pw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/node": "*"
|
"@types/node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/istanbul-lib-coverage": {
|
"node_modules/@types/istanbul-lib-coverage": {
|
||||||
"version": "2.0.4",
|
"version": "2.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz",
|
||||||
"integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==",
|
"integrity": "sha512-zONci81DZYCZjiLe0r6equvZut0b+dBRPBN5kBDjsONnutYNtJMoWQ9uR2RkL1gLG9NMTzvf+29e5RFfPbeKhQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/@types/istanbul-lib-report": {
|
"node_modules/@types/istanbul-lib-report": {
|
||||||
"version": "3.0.1",
|
"version": "3.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.2.tgz",
|
||||||
"integrity": "sha512-gPQuzaPR5h/djlAv2apEG1HVOyj1IUs7GpfMZixU0/0KXT3pm64ylHuMUI1/Akh+sq/iikxg6Z2j+fcMDXaaTQ==",
|
"integrity": "sha512-8toY6FgdltSdONav1XtUHl4LN1yTmLza+EuDazb/fEmRNCwjyqNVIQWs2IfC74IqjHkREs/nQ2FWq5kZU9IC0w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/istanbul-lib-coverage": "*"
|
"@types/istanbul-lib-coverage": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/istanbul-reports": {
|
"node_modules/@types/istanbul-reports": {
|
||||||
"version": "3.0.2",
|
"version": "3.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.3.tgz",
|
||||||
"integrity": "sha512-kv43F9eb3Lhj+lr/Hn6OcLCs/sSM8bt+fIaP11rCYngfV6NVjzWXJ17owQtDQTL9tQ8WSLUrGsSJ6rJz0F1w1A==",
|
"integrity": "sha512-1nESsePMBlf0RPRffLZi5ujYh7IH1BWL4y9pr+Bn3cJBdxz+RTP8bUFljLz9HvzhhOSWKdyBZ4DIivdL6rvgZg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/istanbul-lib-report": "*"
|
"@types/istanbul-lib-report": "*"
|
||||||
|
@ -5428,21 +5429,21 @@
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/@types/js-yaml": {
|
"node_modules/@types/js-yaml": {
|
||||||
"version": "4.0.7",
|
"version": "4.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.8.tgz",
|
||||||
"integrity": "sha512-RJZP9WAMMr1514KbdSXkLRrKvYQacjr1+HWnY8pui/uBTBoSgD9ZGR17u/d4nb9NpERp0FkdLBe7hq8NIPBgkg==",
|
"integrity": "sha512-m6jnPk1VhlYRiLFm3f8X9Uep761f+CK8mHyS65LutH2OhmBF0BeMEjHgg05usH8PLZMWWc/BUR9RPmkvpWnyRA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/@types/json-schema": {
|
"node_modules/@types/json-schema": {
|
||||||
"version": "7.0.13",
|
"version": "7.0.14",
|
||||||
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.13.tgz",
|
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.14.tgz",
|
||||||
"integrity": "sha512-RbSSoHliUbnXj3ny0CNFOoxrIDV6SUGyStHsvDqosw6CkdPV8TtWGlfecuK4ToyMEAql6pzNxgCFKanovUzlgQ==",
|
"integrity": "sha512-U3PUjAudAdJBeC2pgN8uTIKgxrb4nlDF3SF0++EldXQvQBGkpFZMSnwQiIoDU77tv45VgNkl/L4ouD+rEomujw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/@types/json-stable-stringify": {
|
"node_modules/@types/json-stable-stringify": {
|
||||||
"version": "1.0.34",
|
"version": "1.0.35",
|
||||||
"resolved": "https://registry.npmjs.org/@types/json-stable-stringify/-/json-stable-stringify-1.0.34.tgz",
|
"resolved": "https://registry.npmjs.org/@types/json-stable-stringify/-/json-stable-stringify-1.0.35.tgz",
|
||||||
"integrity": "sha512-s2cfwagOQAS8o06TcwKfr9Wx11dNGbH2E9vJz1cqV+a/LOyhWNLUNd6JSRYNzvB4d29UuJX2M0Dj9vE1T8fRXw==",
|
"integrity": "sha512-zlCWqsRBI0+ANN7dzGeDFJ4CHaVFTLqBNRS11GjR2mHCW6XxNtnMxhQzBKMzfsnjI8oI+kWq2vBwinyQpZVSsg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/@types/json5": {
|
"node_modules/@types/json5": {
|
||||||
|
@ -5452,9 +5453,9 @@
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/@types/minimist": {
|
"node_modules/@types/minimist": {
|
||||||
"version": "1.2.3",
|
"version": "1.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.4.tgz",
|
||||||
"integrity": "sha512-ZYFzrvyWUNhaPomn80dsMNgMeXxNWZBdkuG/hWlUvXvbdUH8ZERNBGXnU87McuGcWDsyzX2aChCv/SVN348k3A==",
|
"integrity": "sha512-Kfe/D3hxHTusnPNRbycJE1N77WHDsdS4AjUYIzlDzhDrS47NrwuL3YW4VITxwR7KCVpzwgy4Rbj829KSSQmwXQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/@types/node": {
|
"node_modules/@types/node": {
|
||||||
|
@ -5464,27 +5465,27 @@
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/@types/normalize-package-data": {
|
"node_modules/@types/normalize-package-data": {
|
||||||
"version": "2.4.2",
|
"version": "2.4.3",
|
||||||
"resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.2.tgz",
|
"resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.3.tgz",
|
||||||
"integrity": "sha512-lqa4UEhhv/2sjjIQgjX8B+RBjj47eo0mzGasklVJ78UKGQY1r0VpB9XHDaZZO9qzEFDdy4MrXLuEaSmPrPSe/A==",
|
"integrity": "sha512-ehPtgRgaULsFG8x0NeYJvmyH1hmlfsNLujHe9dQEia/7MAJYdzMSi19JtchUHjmBA6XC/75dK55mzZH+RyieSg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/@types/object.omit": {
|
"node_modules/@types/object.omit": {
|
||||||
"version": "3.0.1",
|
"version": "3.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/@types/object.omit/-/object.omit-3.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@types/object.omit/-/object.omit-3.0.2.tgz",
|
||||||
"integrity": "sha512-24XD34UeRWw505TsMNBrQ4bES2s8IxiFC59mmNUFhTz9IX2hAtA7gQ8wVww1i17QmhBYILg5iqYP2y7aqA3pwQ==",
|
"integrity": "sha512-BxWU36cMP+FKD3OLFluQaj2cBev2sx2LJaHELuphHwnleq+xnEhTmuYYYx4pOT/1U/ZoR6B+RdvxWh2FD6lGGA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/@types/object.pick": {
|
"node_modules/@types/object.pick": {
|
||||||
"version": "1.3.2",
|
"version": "1.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/@types/object.pick/-/object.pick-1.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/@types/object.pick/-/object.pick-1.3.3.tgz",
|
||||||
"integrity": "sha512-sn7L+qQ6RLPdXRoiaE7bZ/Ek+o4uICma/lBFPyJEKDTPTBP1W8u0c4baj3EiS4DiqLs+Hk+KUGvMVJtAw3ePJg==",
|
"integrity": "sha512-qZqHmdGEALeSATMB1djT1S5szv6Wtpb7DKpHrt2XG4iyKlV7C2Xk8GmDXr1KXakOqUfX6ohw7ceruYt4NVmB1Q==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/@types/parse-json": {
|
"node_modules/@types/parse-json": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.1.tgz",
|
||||||
"integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==",
|
"integrity": "sha512-3YmXzzPAdOTVljVMkTMBdBEvlOLg2cDQaDhnnhT3nT9uDbnJzjWhKlzb+desT12Y7tGqaN6d+AbozcKzyL36Ng==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/@types/prettier": {
|
"node_modules/@types/prettier": {
|
||||||
|
@ -5494,15 +5495,15 @@
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/@types/semver": {
|
"node_modules/@types/semver": {
|
||||||
"version": "7.5.3",
|
"version": "7.5.4",
|
||||||
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.3.tgz",
|
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.4.tgz",
|
||||||
"integrity": "sha512-OxepLK9EuNEIPxWNME+C6WwbRAOOI2o2BaQEGzz5Lu2e4Z5eDnEo+/aVEDMIXywoJitJ7xWd641wrGLZdtwRyw==",
|
"integrity": "sha512-MMzuxN3GdFwskAnb6fz0orFvhfqi752yjaXylr0Rp4oDg5H0Zn1IuyRhDVvYOwAXoJirx2xuS16I3WjxnAIHiQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/@types/stack-utils": {
|
"node_modules/@types/stack-utils": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.2.tgz",
|
||||||
"integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==",
|
"integrity": "sha512-g7CK9nHdwjK2n0ymT2CW698FuWJRIx+RP6embAzZ2Qi8/ilIrA1Imt2LVSeHUzKvpoi7BhmmQcXz95eS0f2JXw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/@types/throttle-debounce": {
|
"node_modules/@types/throttle-debounce": {
|
||||||
|
@ -5512,27 +5513,27 @@
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/@types/ws": {
|
"node_modules/@types/ws": {
|
||||||
"version": "8.5.7",
|
"version": "8.5.8",
|
||||||
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.7.tgz",
|
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.8.tgz",
|
||||||
"integrity": "sha512-6UrLjiDUvn40CMrAubXuIVtj2PEfKDffJS7ychvnPU44j+KVeXmdHHTgqcM/dxLUTHxlXHiFM8Skmb8ozGdTnQ==",
|
"integrity": "sha512-flUksGIQCnJd6sZ1l5dqCEG/ksaoAg/eUwiLAGTJQcfgvZJKF++Ta4bJA6A5aPSJmsr+xlseHn4KLgVlNnvPTg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/node": "*"
|
"@types/node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/yargs": {
|
"node_modules/@types/yargs": {
|
||||||
"version": "17.0.28",
|
"version": "17.0.29",
|
||||||
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.28.tgz",
|
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.29.tgz",
|
||||||
"integrity": "sha512-N3e3fkS86hNhtk6BEnc0rj3zcehaxx8QWhCROJkqpl5Zaoi7nAic3jH8q94jVD3zu5LGk+PUB6KAiDmimYOEQw==",
|
"integrity": "sha512-nacjqA3ee9zRF/++a3FUY1suHTFKZeHba2n8WeDw9cCVdmzmHpIxyzOJBcpHvvEmS8E9KqWlSnWHUkOrkhWcvA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/yargs-parser": "*"
|
"@types/yargs-parser": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/yargs-parser": {
|
"node_modules/@types/yargs-parser": {
|
||||||
"version": "21.0.1",
|
"version": "21.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.2.tgz",
|
||||||
"integrity": "sha512-axdPBuLuEJt0c4yI5OZssC19K2Mq1uKdrfZBzuxLvaztgqUtFYZUNw7lETExPYJR9jdEoIg4mb7RQKRQzOkeGQ==",
|
"integrity": "sha512-5qcvofLPbfjmBfKaLfj/+f+Sbd6pN4zl7w7VSVI5uz7m9QZTuB2aZAa2uo1wHFBNN2x6g/SoTkXmd8mQnQF2Cw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||||
|
@ -6868,9 +6869,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/caniuse-lite": {
|
"node_modules/caniuse-lite": {
|
||||||
"version": "1.0.30001549",
|
"version": "1.0.30001550",
|
||||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001549.tgz",
|
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001550.tgz",
|
||||||
"integrity": "sha512-qRp48dPYSCYaP+KurZLhDYdVE+yEyht/3NlmcJgVQ2VMGt6JL36ndQ/7rgspdZsJuxDPFIo/OzBT2+GmIJ53BA==",
|
"integrity": "sha512-p82WjBYIypO0ukTsd/FG3Xxs+4tFeaY9pfT4amQL8KWtYH7H9nYwReGAbMTJ0hsmRO8IfDtsS6p3ZWj8+1c2RQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
|
@ -7913,9 +7914,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/electron-to-chromium": {
|
"node_modules/electron-to-chromium": {
|
||||||
"version": "1.4.556",
|
"version": "1.4.559",
|
||||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.556.tgz",
|
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.559.tgz",
|
||||||
"integrity": "sha512-6RPN0hHfzDU8D56E72YkDvnLw5Cj2NMXZGg3UkgyoHxjVhG99KZpsKgBWMmTy0Ei89xwan+rbRsVB9yzATmYzQ==",
|
"integrity": "sha512-iS7KhLYCSJbdo3rUSkhDTVuFNCV34RKs2UaB9Ecr7VlqzjjWW//0nfsFF5dtDmyXlZQaDYYtID5fjtC/6lpRug==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/emittery": {
|
"node_modules/emittery": {
|
||||||
|
@ -11439,6 +11440,15 @@
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/javascript-time-ago": {
|
||||||
|
"version": "2.5.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/javascript-time-ago/-/javascript-time-ago-2.5.9.tgz",
|
||||||
|
"integrity": "sha512-pQ8mNco/9g9TqWXWWjP0EWl6i/lAQScOyEeXy5AB+f7MfLSdgyV9BJhiOD1zrIac/lrxPYOWNbyl/IW8CW5n0A==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"relative-time-format": "^1.1.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/jest": {
|
"node_modules/jest": {
|
||||||
"version": "29.7.0",
|
"version": "29.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz",
|
||||||
|
@ -16250,6 +16260,12 @@
|
||||||
"jsesc": "bin/jsesc"
|
"jsesc": "bin/jsesc"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/relative-time-format": {
|
||||||
|
"version": "1.1.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/relative-time-format/-/relative-time-format-1.1.6.tgz",
|
||||||
|
"integrity": "sha512-aCv3juQw4hT1/P/OrVltKWLlp15eW1GRcwP1XdxHrPdZE9MtgqFpegjnTjLhi2m2WI9MT/hQQtE+tjEWG1hgkQ==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/relay-runtime": {
|
"node_modules/relay-runtime": {
|
||||||
"version": "12.0.0",
|
"version": "12.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/relay-runtime/-/relay-runtime-12.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/relay-runtime/-/relay-runtime-12.0.0.tgz",
|
||||||
|
|
|
@ -117,6 +117,7 @@
|
||||||
"husky": "8.0.3",
|
"husky": "8.0.3",
|
||||||
"hygen": "6.2.11",
|
"hygen": "6.2.11",
|
||||||
"i18next-http-backend": "2.2.0",
|
"i18next-http-backend": "2.2.0",
|
||||||
|
"javascript-time-ago": "2.5.9",
|
||||||
"jest": "29.7.0",
|
"jest": "29.7.0",
|
||||||
"js-cookie": "3.0.5",
|
"js-cookie": "3.0.5",
|
||||||
"lint-staged": "14.0.1",
|
"lint-staged": "14.0.1",
|
||||||
|
|
|
@ -210,21 +210,17 @@
|
||||||
"New only": "New only",
|
"New only": "New only",
|
||||||
"New password": "New password",
|
"New password": "New password",
|
||||||
"New stories every day and even more!": "New stories and more are waiting for you every day!",
|
"New stories every day and even more!": "New stories and more are waiting for you every day!",
|
||||||
|
|
||||||
"NotificationNewCommentText1": "{commentsCount, plural, one {New comment} other {{commentsCount} comments}} to your publication",
|
|
||||||
"NotificationNewCommentText2": "from",
|
|
||||||
"NotificationNewCommentText3": "{restUsersCount, plural, =0 {} one { one more user} other { and more {restUsersCount} users}}",
|
|
||||||
|
|
||||||
"NotificationNewReplyText1": "{commentsCount, plural, one {New reply} other {{commentsCount} replays}} to your publication",
|
|
||||||
"NotificationNewReplyText2": "from",
|
|
||||||
"NotificationNewReplyText3": "{restUsersCount, plural, =0 {} one { and one more user} other { and more {restUsersCount} users}}",
|
|
||||||
|
|
||||||
"Newsletter": "Newsletter",
|
"Newsletter": "Newsletter",
|
||||||
"Night mode": "Night mode",
|
"Night mode": "Night mode",
|
||||||
"No notifications yet": "No notifications yet",
|
"No notifications yet": "No notifications yet",
|
||||||
"Write good articles, comment\nand it won't be so empty here": "Write good articles, comment\nand it won't be so empty here",
|
|
||||||
"Nothing here yet": "There's nothing here yet",
|
"Nothing here yet": "There's nothing here yet",
|
||||||
"Nothing is here": "There is nothing here",
|
"Nothing is here": "There is nothing here",
|
||||||
|
"NotificationNewCommentText1": "{commentsCount, plural, one {New comment} other {{commentsCount} comments}} to your publication",
|
||||||
|
"NotificationNewCommentText2": "from",
|
||||||
|
"NotificationNewCommentText3": "{restUsersCount, plural, =0 {} one { one more user} other { and more {restUsersCount} users}}",
|
||||||
|
"NotificationNewReplyText1": "{commentsCount, plural, one {New reply} other {{commentsCount} replays}} to your publication",
|
||||||
|
"NotificationNewReplyText2": "from",
|
||||||
|
"NotificationNewReplyText3": "{restUsersCount, plural, =0 {} one { and one more user} other { and more {restUsersCount} users}}",
|
||||||
"Notifications": "Notifications",
|
"Notifications": "Notifications",
|
||||||
"Or paste a link to an image": "Or paste a link to an image",
|
"Or paste a link to an image": "Or paste a link to an image",
|
||||||
"Ordered list": "Ordered list",
|
"Ordered list": "Ordered list",
|
||||||
|
@ -369,6 +365,7 @@
|
||||||
"Write about the topic": "Write about the topic",
|
"Write about the topic": "Write about the topic",
|
||||||
"Write an article": "Write an article",
|
"Write an article": "Write an article",
|
||||||
"Write comment": "Write comment",
|
"Write comment": "Write comment",
|
||||||
|
"Write good articles, comment\nand it won't be so empty here": "Write good articles, comment\nand it won't be so empty here",
|
||||||
"Write message": "Write a message",
|
"Write message": "Write a message",
|
||||||
"Write to us": "Write to us",
|
"Write to us": "Write to us",
|
||||||
"You can download multiple tracks at once in .mp3, .wav or .flac formats": "You can download multiple tracks at once in .mp3, .wav or .flac formats",
|
"You can download multiple tracks at once in .mp3, .wav or .flac formats": "You can download multiple tracks at once in .mp3, .wav or .flac formats",
|
||||||
|
@ -385,6 +382,7 @@
|
||||||
"article": "article",
|
"article": "article",
|
||||||
"author": "author",
|
"author": "author",
|
||||||
"authors": "authors",
|
"authors": "authors",
|
||||||
|
"authorsWithCount": "{count} {count, plural, one {author} other {authors}}",
|
||||||
"back to menu": "back to menu",
|
"back to menu": "back to menu",
|
||||||
"bold": "bold",
|
"bold": "bold",
|
||||||
"bookmarks": "bookmarks",
|
"bookmarks": "bookmarks",
|
||||||
|
@ -395,10 +393,12 @@
|
||||||
"delimiter": "delimiter",
|
"delimiter": "delimiter",
|
||||||
"discussion": "discourse",
|
"discussion": "discourse",
|
||||||
"drafts": "drafts",
|
"drafts": "drafts",
|
||||||
|
"earlier": "earlier",
|
||||||
"email not confirmed": "email not confirmed",
|
"email not confirmed": "email not confirmed",
|
||||||
"enter": "enter",
|
"enter": "enter",
|
||||||
"feed": "feed",
|
"feed": "feed",
|
||||||
"follower": "follower",
|
"follower": "follower",
|
||||||
|
"followersWithCount": "{count} {count, plural, one {follower} other {followers}}",
|
||||||
"general feed": "general tape",
|
"general feed": "general tape",
|
||||||
"header 1": "header 1",
|
"header 1": "header 1",
|
||||||
"header 2": "header 2",
|
"header 2": "header 2",
|
||||||
|
@ -420,6 +420,7 @@
|
||||||
"register": "register",
|
"register": "register",
|
||||||
"repeat": "repeat",
|
"repeat": "repeat",
|
||||||
"shout": "post",
|
"shout": "post",
|
||||||
|
"shoutsWithCount": "{count} {count, plural, one {post} other {posts}}",
|
||||||
"sign up or sign in": "sign up or sign in",
|
"sign up or sign in": "sign up or sign in",
|
||||||
"slug is used by another user": "Slug is already taken by another user",
|
"slug is used by another user": "Slug is already taken by another user",
|
||||||
"subscriber": "subscriber",
|
"subscriber": "subscriber",
|
||||||
|
@ -429,8 +430,10 @@
|
||||||
"subscription_rp": "subscription",
|
"subscription_rp": "subscription",
|
||||||
"subscriptions": "subscriptions",
|
"subscriptions": "subscriptions",
|
||||||
"terms of use": "terms of use",
|
"terms of use": "terms of use",
|
||||||
|
"today": "today",
|
||||||
"topics": "topics",
|
"topics": "topics",
|
||||||
"user already exist": "user already exists",
|
"user already exist": "user already exists",
|
||||||
"video": "video",
|
"video": "video",
|
||||||
"view": "view"
|
"view": "view",
|
||||||
|
"yesterday": "yesterday"
|
||||||
}
|
}
|
||||||
|
|
|
@ -220,22 +220,18 @@
|
||||||
"New only": "Только новые",
|
"New only": "Только новые",
|
||||||
"New password": "Новый пароль",
|
"New password": "Новый пароль",
|
||||||
"New stories every day and even more!": "Каждый день вас ждут новые истории и ещё много всего интересного!",
|
"New stories every day and even more!": "Каждый день вас ждут новые истории и ещё много всего интересного!",
|
||||||
|
|
||||||
"NotificationNewCommentText1": "{commentsCount, plural, one {Новый комментарий} few {{commentsCount} новых комментария} other {{commentsCount} новых комментариев}} к вашей публикации",
|
|
||||||
"NotificationNewCommentText2": "от",
|
|
||||||
"NotificationNewCommentText3": "{restUsersCount, plural, =0 {} one { и ещё 1 пользователя} few { и ещё {restUsersCount} пользователей} other { и ещё {restUsersCount} пользователей}}",
|
|
||||||
|
|
||||||
"NotificationNewReplyText1": "{commentsCount, plural, one {Новый ответ} few {{commentsCount} новых ответа} other {{commentsCount} новых ответов}} на ваш комментарий к публикации",
|
|
||||||
"NotificationNewReplyText2": "от",
|
|
||||||
"NotificationNewReplyText3": "{restUsersCount, plural, =0 {} one { и ещё 1 пользователя} few { и ещё {restUsersCount} пользователей} other { и ещё {restUsersCount} пользователей}}",
|
|
||||||
|
|
||||||
"Newsletter": "Рассылка",
|
"Newsletter": "Рассылка",
|
||||||
"Night mode": "Ночная тема",
|
"Night mode": "Ночная тема",
|
||||||
"No notifications yet": "Уведомлений пока нет",
|
"No notifications yet": "Уведомлений пока нет",
|
||||||
"Write good articles, comment\nand it won't be so empty here": "Пишите хорошие статьи, комментируйте,\nи здесь станет не так пусто",
|
|
||||||
"No such account, please try to register": "Такой адрес не найден, попробуйте зарегистрироваться",
|
"No such account, please try to register": "Такой адрес не найден, попробуйте зарегистрироваться",
|
||||||
"Nothing here yet": "Здесь пока ничего нет",
|
"Nothing here yet": "Здесь пока ничего нет",
|
||||||
"Nothing is here": "Здесь ничего нет",
|
"Nothing is here": "Здесь ничего нет",
|
||||||
|
"NotificationNewCommentText1": "{commentsCount, plural, one {Новый комментарий} few {{commentsCount} новых комментария} other {{commentsCount} новых комментариев}} к вашей публикации",
|
||||||
|
"NotificationNewCommentText2": "от",
|
||||||
|
"NotificationNewCommentText3": "{restUsersCount, plural, =0 {} one { и ещё 1 пользователя} few { и ещё {restUsersCount} пользователей} other { и ещё {restUsersCount} пользователей}}",
|
||||||
|
"NotificationNewReplyText1": "{commentsCount, plural, one {Новый ответ} few {{commentsCount} новых ответа} other {{commentsCount} новых ответов}} на ваш комментарий к публикации",
|
||||||
|
"NotificationNewReplyText2": "от",
|
||||||
|
"NotificationNewReplyText3": "{restUsersCount, plural, =0 {} one { и ещё 1 пользователя} few { и ещё {restUsersCount} пользователей} other { и ещё {restUsersCount} пользователей}}",
|
||||||
"Notifications": "Уведомления",
|
"Notifications": "Уведомления",
|
||||||
"Or paste a link to an image": "Или вставьте ссылку на изображение",
|
"Or paste a link to an image": "Или вставьте ссылку на изображение",
|
||||||
"Ordered list": "Нумерованный список",
|
"Ordered list": "Нумерованный список",
|
||||||
|
@ -389,6 +385,7 @@
|
||||||
"Write about the topic": "Написать в тему",
|
"Write about the topic": "Написать в тему",
|
||||||
"Write an article": "Написать статью",
|
"Write an article": "Написать статью",
|
||||||
"Write comment": "Написать комментарий",
|
"Write comment": "Написать комментарий",
|
||||||
|
"Write good articles, comment\nand it won't be so empty here": "Пишите хорошие статьи, комментируйте,\nи здесь станет не так пусто",
|
||||||
"Write message": "Написать сообщение",
|
"Write message": "Написать сообщение",
|
||||||
"Write to us": "Напишите нам",
|
"Write to us": "Напишите нам",
|
||||||
"You can download multiple tracks at once in .mp3, .wav or .flac formats": "Можно загрузить сразу несколько треков в форматах .mp3, .wav или .flac",
|
"You can download multiple tracks at once in .mp3, .wav or .flac formats": "Можно загрузить сразу несколько треков в форматах .mp3, .wav или .flac",
|
||||||
|
@ -405,6 +402,7 @@
|
||||||
"article": "статья",
|
"article": "статья",
|
||||||
"author": "автор",
|
"author": "автор",
|
||||||
"authors": "авторы",
|
"authors": "авторы",
|
||||||
|
"authorsWithCount": "{count} {count, plural, one {автор} few {автора} other {авторов}}",
|
||||||
"back to menu": "назад в меню",
|
"back to menu": "назад в меню",
|
||||||
"bold": "жирный",
|
"bold": "жирный",
|
||||||
"bookmarks": "закладки",
|
"bookmarks": "закладки",
|
||||||
|
@ -418,10 +416,12 @@
|
||||||
"discourse_theme": "Тема дискурса",
|
"discourse_theme": "Тема дискурса",
|
||||||
"discussion": "дискурс",
|
"discussion": "дискурс",
|
||||||
"drafts": "черновики",
|
"drafts": "черновики",
|
||||||
|
"earlier": "ранее",
|
||||||
"email not confirmed": "email не подтвержден",
|
"email not confirmed": "email не подтвержден",
|
||||||
"enter": "войдите",
|
"enter": "войдите",
|
||||||
"feed": "лента",
|
"feed": "лента",
|
||||||
"follower": "подписчик",
|
"follower": "подписчик",
|
||||||
|
"followersWithCount": "{count} {count, plural, one {подписчик} few {подписчика} other {подписчиков}}",
|
||||||
"general feed": "Общая лента",
|
"general feed": "Общая лента",
|
||||||
"header 1": "заголовок 1",
|
"header 1": "заголовок 1",
|
||||||
"header 2": "заголовок 2",
|
"header 2": "заголовок 2",
|
||||||
|
@ -444,6 +444,7 @@
|
||||||
"register": "зарегистрируйтесь",
|
"register": "зарегистрируйтесь",
|
||||||
"repeat": "повторить",
|
"repeat": "повторить",
|
||||||
"shout": "пост",
|
"shout": "пост",
|
||||||
|
"shoutsWithCount": "{count} {count, plural, one {пост} few {поста} other {постов}}",
|
||||||
"sign in": "войти",
|
"sign in": "войти",
|
||||||
"sign up": "зарегистрироваться",
|
"sign up": "зарегистрироваться",
|
||||||
"sign up or sign in": "зарегистрироваться или войти",
|
"sign up or sign in": "зарегистрироваться или войти",
|
||||||
|
@ -453,8 +454,10 @@
|
||||||
"subscriber_rp": "подписчика",
|
"subscriber_rp": "подписчика",
|
||||||
"subscribers": "подписчиков",
|
"subscribers": "подписчиков",
|
||||||
"terms of use": "правилами пользования сайтом",
|
"terms of use": "правилами пользования сайтом",
|
||||||
|
"today": "сегодня",
|
||||||
"topics": "темы",
|
"topics": "темы",
|
||||||
"user already exist": "пользователь уже существует",
|
"user already exist": "пользователь уже существует",
|
||||||
"video": "видео",
|
"video": "видео",
|
||||||
"view": "просмотр"
|
"view": "просмотр",
|
||||||
|
"yesterday": "вчера"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import { Show } from 'solid-js'
|
import { Show } from 'solid-js'
|
||||||
import { Icon } from '../_shared/Icon'
|
import { Icon } from '../_shared/Icon'
|
||||||
import type { Reaction } from '../../graphql/types.gen'
|
import type { Reaction } from '../../graphql/types.gen'
|
||||||
import { formatDate } from '../../utils'
|
|
||||||
import { useLocalize } from '../../context/localize'
|
import { useLocalize } from '../../context/localize'
|
||||||
import { clsx } from 'clsx'
|
import { clsx } from 'clsx'
|
||||||
import styles from './CommentDate.module.scss'
|
import styles from './CommentDate.module.scss'
|
||||||
|
@ -13,9 +12,9 @@ type Props = {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CommentDate = (props: Props) => {
|
export const CommentDate = (props: Props) => {
|
||||||
const { t } = useLocalize()
|
const { t, formatDate } = useLocalize()
|
||||||
|
|
||||||
const formattedDate = (date) => {
|
const formattedDate = (date: number) => {
|
||||||
const formatDateOptions: Intl.DateTimeFormatOptions = props.isShort
|
const formatDateOptions: Intl.DateTimeFormatOptions = props.isShort
|
||||||
? { month: 'long', day: 'numeric', year: 'numeric' }
|
? { month: 'long', day: 'numeric', year: 'numeric' }
|
||||||
: { hour: 'numeric', minute: 'numeric' }
|
: { hour: 'numeric', minute: 'numeric' }
|
||||||
|
|
|
@ -8,8 +8,7 @@ import { useSession } from '../../context/session'
|
||||||
import { useLocalize } from '../../context/localize'
|
import { useLocalize } from '../../context/localize'
|
||||||
import { useReactions } from '../../context/reactions'
|
import { useReactions } from '../../context/reactions'
|
||||||
import { MediaItem } from '../../pages/types'
|
import { MediaItem } from '../../pages/types'
|
||||||
import { router, useRouter } from '../../stores/router'
|
import { DEFAULT_HEADER_OFFSET, router, useRouter } from '../../stores/router'
|
||||||
import { formatDate } from '../../utils'
|
|
||||||
import { getDescription } from '../../utils/meta'
|
import { getDescription } from '../../utils/meta'
|
||||||
import { imageProxy } from '../../utils/imageProxy'
|
import { imageProxy } from '../../utils/imageProxy'
|
||||||
import { AuthorCard } from '../Author/AuthorCard'
|
import { AuthorCard } from '../Author/AuthorCard'
|
||||||
|
@ -42,14 +41,14 @@ const scrollTo = (el: HTMLElement) => {
|
||||||
const { top } = el.getBoundingClientRect()
|
const { top } = el.getBoundingClientRect()
|
||||||
|
|
||||||
window.scrollTo({
|
window.scrollTo({
|
||||||
top: top + window.scrollY - 96,
|
top: top + window.scrollY - DEFAULT_HEADER_OFFSET,
|
||||||
left: 0,
|
left: 0,
|
||||||
behavior: 'smooth'
|
behavior: 'smooth'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export const FullArticle = (props: Props) => {
|
export const FullArticle = (props: Props) => {
|
||||||
const { t } = useLocalize()
|
const { t, formatDate } = useLocalize()
|
||||||
const {
|
const {
|
||||||
user,
|
user,
|
||||||
isAuthenticated,
|
isAuthenticated,
|
||||||
|
|
|
@ -3,7 +3,6 @@ import styles from './AuthorBadge.module.scss'
|
||||||
import { Userpic } from '../Userpic'
|
import { Userpic } from '../Userpic'
|
||||||
import { Author, FollowingEntity } from '../../../graphql/types.gen'
|
import { Author, FollowingEntity } from '../../../graphql/types.gen'
|
||||||
import { createMemo, createSignal, Match, Show, Switch } from 'solid-js'
|
import { createMemo, createSignal, Match, Show, Switch } from 'solid-js'
|
||||||
import { formatDate } from '../../../utils'
|
|
||||||
import { useLocalize } from '../../../context/localize'
|
import { useLocalize } from '../../../context/localize'
|
||||||
import { Button } from '../../_shared/Button'
|
import { Button } from '../../_shared/Button'
|
||||||
import { useSession } from '../../../context/session'
|
import { useSession } from '../../../context/session'
|
||||||
|
@ -21,7 +20,7 @@ export const AuthorBadge = (props: Props) => {
|
||||||
actions: { loadSession, requireAuthentication }
|
actions: { loadSession, requireAuthentication }
|
||||||
} = useSession()
|
} = useSession()
|
||||||
|
|
||||||
const { t } = useLocalize()
|
const { t, formatDate } = useLocalize()
|
||||||
const subscribed = createMemo<boolean>(() => {
|
const subscribed = createMemo<boolean>(() => {
|
||||||
return session()?.news?.authors?.some((u) => u === props.author.slug) || false
|
return session()?.news?.authors?.some((u) => u === props.author.slug) || false
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
import type { Author } from '../../../graphql/types.gen'
|
import type { Author } from '../../../graphql/types.gen'
|
||||||
import { Userpic } from '../Userpic'
|
import { Userpic } from '../Userpic'
|
||||||
import { Icon } from '../../_shared/Icon'
|
import { Icon } from '../../_shared/Icon'
|
||||||
import styles from './AuthorCard.module.scss'
|
import { createEffect, createMemo, createSignal, For, Show } from 'solid-js'
|
||||||
import { createEffect, createMemo, createSignal, For, Match, Show, Switch } from 'solid-js'
|
|
||||||
import { translit } from '../../../utils/ru2en'
|
import { translit } from '../../../utils/ru2en'
|
||||||
import { follow, unfollow } from '../../../stores/zine/common'
|
import { follow, unfollow } from '../../../stores/zine/common'
|
||||||
import { clsx } from 'clsx'
|
import { clsx } from 'clsx'
|
||||||
|
@ -20,7 +19,7 @@ import { AuthorBadge } from '../AuthorBadge'
|
||||||
import { TopicBadge } from '../../Topic/TopicBadge'
|
import { TopicBadge } from '../../Topic/TopicBadge'
|
||||||
import { Button } from '../../_shared/Button'
|
import { Button } from '../../_shared/Button'
|
||||||
import { getShareUrl, SharePopup } from '../../Article/SharePopup'
|
import { getShareUrl, SharePopup } from '../../Article/SharePopup'
|
||||||
import stylesHeader from '../../Nav/Header/Header.module.scss'
|
import styles from './AuthorCard.module.scss'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
caption?: string
|
caption?: string
|
||||||
|
|
|
@ -2,8 +2,6 @@ import { clsx } from 'clsx'
|
||||||
import styles from './Draft.module.scss'
|
import styles from './Draft.module.scss'
|
||||||
import type { Shout } from '../../graphql/types.gen'
|
import type { Shout } from '../../graphql/types.gen'
|
||||||
import { Icon } from '../_shared/Icon'
|
import { Icon } from '../_shared/Icon'
|
||||||
import { formatDate } from '../../utils'
|
|
||||||
import formatDateTime from '../../utils/formatDateTime'
|
|
||||||
import { useLocalize } from '../../context/localize'
|
import { useLocalize } from '../../context/localize'
|
||||||
import { useConfirm } from '../../context/confirm'
|
import { useConfirm } from '../../context/confirm'
|
||||||
import { useSnackbar } from '../../context/snackbar'
|
import { useSnackbar } from '../../context/snackbar'
|
||||||
|
@ -18,7 +16,7 @@ type Props = {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Draft = (props: Props) => {
|
export const Draft = (props: Props) => {
|
||||||
const { t } = useLocalize()
|
const { t, formatDate } = useLocalize()
|
||||||
const {
|
const {
|
||||||
actions: { showConfirm }
|
actions: { showConfirm }
|
||||||
} = useConfirm()
|
} = useConfirm()
|
||||||
|
@ -51,8 +49,8 @@ export const Draft = (props: Props) => {
|
||||||
return (
|
return (
|
||||||
<div class={clsx(props.class)}>
|
<div class={clsx(props.class)}>
|
||||||
<div class={styles.created}>
|
<div class={styles.created}>
|
||||||
<Icon name="pencil-outline" class={styles.icon} /> {formatDate(new Date(props.shout.createdAt))}
|
<Icon name="pencil-outline" class={styles.icon} />{' '}
|
||||||
{formatDateTime(props.shout.createdAt)()}
|
{formatDate(new Date(props.shout.createdAt), { hour: '2-digit', minute: '2-digit' })}
|
||||||
</div>
|
</div>
|
||||||
<div class={styles.titleContainer}>
|
<div class={styles.titleContainer}>
|
||||||
<span class={styles.title}>{props.shout.title || t('Unnamed draft')}</span> {props.shout.subtitle}
|
<span class={styles.title}>{props.shout.title || t('Unnamed draft')}</span> {props.shout.subtitle}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import { createMemo, createSignal, For, Show } from 'solid-js'
|
import { createMemo, createSignal, For, Show } from 'solid-js'
|
||||||
import type { Shout } from '../../graphql/types.gen'
|
import type { Shout } from '../../graphql/types.gen'
|
||||||
import { capitalize, formatDate } from '../../utils'
|
|
||||||
import { Icon } from '../_shared/Icon'
|
import { Icon } from '../_shared/Icon'
|
||||||
import styles from './ArticleCard.module.scss'
|
import styles from './ArticleCard.module.scss'
|
||||||
import { clsx } from 'clsx'
|
import { clsx } from 'clsx'
|
||||||
|
@ -17,6 +16,7 @@ import { imageProxy } from '../../utils/imageProxy'
|
||||||
import { Popover } from '../_shared/Popover'
|
import { Popover } from '../_shared/Popover'
|
||||||
import { AuthorCard } from '../Author/AuthorCard'
|
import { AuthorCard } from '../Author/AuthorCard'
|
||||||
import { useSession } from '../../context/session'
|
import { useSession } from '../../context/session'
|
||||||
|
import { capitalize } from '../../utils/capitalize'
|
||||||
|
|
||||||
interface ArticleCardProps {
|
interface ArticleCardProps {
|
||||||
settings?: {
|
settings?: {
|
||||||
|
@ -44,7 +44,12 @@ interface ArticleCardProps {
|
||||||
article: Shout
|
article: Shout
|
||||||
}
|
}
|
||||||
|
|
||||||
const getTitleAndSubtitle = (article: Shout): { title: string; subtitle: string } => {
|
const getTitleAndSubtitle = (
|
||||||
|
article: Shout
|
||||||
|
): {
|
||||||
|
title: string
|
||||||
|
subtitle: string
|
||||||
|
} => {
|
||||||
let title = article.title
|
let title = article.title
|
||||||
let subtitle = article.subtitle
|
let subtitle = article.subtitle
|
||||||
|
|
||||||
|
@ -66,14 +71,14 @@ const getTitleAndSubtitle = (article: Shout): { title: string; subtitle: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ArticleCard = (props: ArticleCardProps) => {
|
export const ArticleCard = (props: ArticleCardProps) => {
|
||||||
const { t, lang } = useLocalize()
|
const { t, lang, formatDate } = useLocalize()
|
||||||
const { user } = useSession()
|
const { user } = useSession()
|
||||||
const mainTopic =
|
const mainTopic =
|
||||||
props.article.topics.find((articleTopic) => articleTopic.slug === props.article.mainTopic) ||
|
props.article.topics.find((articleTopic) => articleTopic.slug === props.article.mainTopic) ||
|
||||||
props.article.topics[0]
|
props.article.topics[0]
|
||||||
|
|
||||||
const formattedDate = createMemo<string>(() => {
|
const formattedDate = createMemo<string>(() => {
|
||||||
return formatDate(new Date(props.article.createdAt), { month: 'long', day: 'numeric', year: 'numeric' })
|
return formatDate(new Date(props.article.createdAt))
|
||||||
})
|
})
|
||||||
|
|
||||||
const { title, subtitle } = getTitleAndSubtitle(props.article)
|
const { title, subtitle } = getTitleAndSubtitle(props.article)
|
||||||
|
|
|
@ -2,7 +2,6 @@ import { Show, Switch, Match, createMemo } from 'solid-js'
|
||||||
import DialogAvatar from './DialogAvatar'
|
import DialogAvatar from './DialogAvatar'
|
||||||
import type { ChatMember } from '../../graphql/types.gen'
|
import type { ChatMember } from '../../graphql/types.gen'
|
||||||
import GroupDialogAvatar from './GroupDialogAvatar'
|
import GroupDialogAvatar from './GroupDialogAvatar'
|
||||||
import formattedTime from '../../utils/formatDateTime'
|
|
||||||
import { clsx } from 'clsx'
|
import { clsx } from 'clsx'
|
||||||
import styles from './DialogCard.module.scss'
|
import styles from './DialogCard.module.scss'
|
||||||
import { useLocalize } from '../../context/localize'
|
import { useLocalize } from '../../context/localize'
|
||||||
|
@ -20,7 +19,7 @@ type DialogProps = {
|
||||||
}
|
}
|
||||||
|
|
||||||
const DialogCard = (props: DialogProps) => {
|
const DialogCard = (props: DialogProps) => {
|
||||||
const { t } = useLocalize()
|
const { t, formatTime } = useLocalize()
|
||||||
const companions = createMemo(
|
const companions = createMemo(
|
||||||
() => props.members && props.members.filter((member) => member.id !== props.ownId)
|
() => props.members && props.members.filter((member) => member.id !== props.ownId)
|
||||||
)
|
)
|
||||||
|
@ -64,7 +63,7 @@ const DialogCard = (props: DialogProps) => {
|
||||||
<Show when={!props.isChatHeader}>
|
<Show when={!props.isChatHeader}>
|
||||||
<div class={styles.activity}>
|
<div class={styles.activity}>
|
||||||
<Show when={props.lastUpdate}>
|
<Show when={props.lastUpdate}>
|
||||||
<div class={styles.time}>{formattedTime(props.lastUpdate * 1000)()}</div>
|
<div class={styles.time}>{formatTime(new Date(props.lastUpdate * 1000))}</div>
|
||||||
</Show>
|
</Show>
|
||||||
<Show when={props.counter > 0}>
|
<Show when={props.counter > 0}>
|
||||||
<div class={styles.counter}>
|
<div class={styles.counter}>
|
||||||
|
|
|
@ -3,10 +3,10 @@ import { clsx } from 'clsx'
|
||||||
import styles from './Message.module.scss'
|
import styles from './Message.module.scss'
|
||||||
import DialogAvatar from './DialogAvatar'
|
import DialogAvatar from './DialogAvatar'
|
||||||
import type { Message as MessageType, ChatMember } from '../../graphql/types.gen'
|
import type { Message as MessageType, ChatMember } from '../../graphql/types.gen'
|
||||||
import formattedTime from '../../utils/formatDateTime'
|
|
||||||
import { Icon } from '../_shared/Icon'
|
import { Icon } from '../_shared/Icon'
|
||||||
import { MessageActionsPopup } from './MessageActionsPopup'
|
import { MessageActionsPopup } from './MessageActionsPopup'
|
||||||
import QuotedMessage from './QuotedMessage'
|
import QuotedMessage from './QuotedMessage'
|
||||||
|
import { useLocalize } from '../../context/localize'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
content: MessageType
|
content: MessageType
|
||||||
|
@ -18,6 +18,7 @@ type Props = {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Message = (props: Props) => {
|
export const Message = (props: Props) => {
|
||||||
|
const { formatTime } = useLocalize()
|
||||||
const isOwn = props.ownId === Number(props.content.author)
|
const isOwn = props.ownId === Number(props.content.author)
|
||||||
const user = props.members?.find((m) => m.id === Number(props.content.author))
|
const user = props.members?.find((m) => m.id === Number(props.content.author))
|
||||||
const [isPopupVisible, setIsPopupVisible] = createSignal<boolean>(false)
|
const [isPopupVisible, setIsPopupVisible] = createSignal<boolean>(false)
|
||||||
|
@ -47,7 +48,7 @@ export const Message = (props: Props) => {
|
||||||
<div innerHTML={props.content.body} />
|
<div innerHTML={props.content.body} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class={styles.time}>{formattedTime(props.content.createdAt * 1000)()}</div>
|
<div class={styles.time}>{formatTime(new Date(props.content.createdAt * 1000))}</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,4 +36,9 @@
|
||||||
.timeContainer {
|
.timeContainer {
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
padding-left: 16px;
|
padding-left: 16px;
|
||||||
|
color: var(--black-400);
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 16px;
|
||||||
|
align-self: flex-start;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,19 @@
|
||||||
import { clsx } from 'clsx'
|
import { clsx } from 'clsx'
|
||||||
import styles from './NotificationView.module.scss'
|
|
||||||
import { formatDate } from '../../../utils'
|
|
||||||
import { createMemo, createSignal, onMount, Show } from 'solid-js'
|
import { createMemo, createSignal, onMount, Show } from 'solid-js'
|
||||||
import { Author } from '../../../graphql/types.gen'
|
import { Author } from '../../../graphql/types.gen'
|
||||||
import { openPage } from '@nanostores/router'
|
import { openPage } from '@nanostores/router'
|
||||||
import { router } from '../../../stores/router'
|
import { router, useRouter } from '../../../stores/router'
|
||||||
import { ServerNotification, useNotifications } from '../../../context/notifications'
|
import { ServerNotification, useNotifications } from '../../../context/notifications'
|
||||||
import { Userpic } from '../../Author/Userpic'
|
import { Userpic } from '../../Author/Userpic'
|
||||||
import { useLocalize } from '../../../context/localize'
|
import { useLocalize } from '../../../context/localize'
|
||||||
|
import type { ArticlePageSearchParams } from '../../Article/FullArticle'
|
||||||
|
import { TimeAgo } from '../../_shared/TimeAgo'
|
||||||
|
import styles from './NotificationView.module.scss'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
notification: ServerNotification
|
notification: ServerNotification
|
||||||
onClick: () => void
|
onClick: () => void
|
||||||
|
dateTimeFormat: 'ago' | 'time' | 'date'
|
||||||
class?: string
|
class?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,9 +51,11 @@ export const NotificationView = (props: Props) => {
|
||||||
const {
|
const {
|
||||||
actions: { markNotificationAsRead }
|
actions: { markNotificationAsRead }
|
||||||
} = useNotifications()
|
} = useNotifications()
|
||||||
const { t } = useLocalize()
|
|
||||||
const [data, setData] = createSignal<ServerNotification>(null)
|
const [data, setData] = createSignal<ServerNotification>(null)
|
||||||
const [kind, setKind] = createSignal<NotificationType>()
|
const [kind, setKind] = createSignal<NotificationType>()
|
||||||
|
const { changeSearchParam } = useRouter<ArticlePageSearchParams>()
|
||||||
|
const { t, formatDate, formatTime } = useLocalize()
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
setTimeout(() => setData(props.notification))
|
setTimeout(() => setData(props.notification))
|
||||||
})
|
})
|
||||||
|
@ -110,6 +114,20 @@ export const NotificationView = (props: Props) => {
|
||||||
props.onClick()
|
props.onClick()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const formattedDateTime = createMemo(() => {
|
||||||
|
switch (props.dateTimeFormat) {
|
||||||
|
case 'ago': {
|
||||||
|
return <TimeAgo date={props.notification.timestamp} />
|
||||||
|
}
|
||||||
|
case 'time': {
|
||||||
|
return formatTime(new Date(props.notification.timestamp))
|
||||||
|
}
|
||||||
|
case 'date': {
|
||||||
|
return formatDate(new Date(props.notification.timestamp), { month: 'numeric', year: '2-digit' })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Show when={data()}>
|
<Show when={data()}>
|
||||||
<div
|
<div
|
||||||
|
@ -120,9 +138,7 @@ export const NotificationView = (props: Props) => {
|
||||||
>
|
>
|
||||||
<Userpic name={lastUser().name} userpic={lastUser().userpic} class={styles.userpic} />
|
<Userpic name={lastUser().name} userpic={lastUser().userpic} class={styles.userpic} />
|
||||||
<div>{content()}</div>
|
<div>{content()}</div>
|
||||||
<div class={styles.timeContainer}>
|
<div class={styles.timeContainer}>{formattedDateTime()}</div>
|
||||||
{/*{formatDate(new Date(props.notification.timestamp), { month: 'numeric' })}*/}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
)
|
)
|
||||||
|
|
|
@ -64,3 +64,12 @@ $transition-duration: 200ms;
|
||||||
.emptyMessageContainer {
|
.emptyMessageContainer {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.periodTitle {
|
||||||
|
// TODO: check markup
|
||||||
|
margin: 32px 0 16px 0;
|
||||||
|
color: var(--black-400);
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 14px;
|
||||||
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { useEscKeyDownHandler } from '../../utils/useEscKeyDownHandler'
|
||||||
import { useOutsideClickHandler } from '../../utils/useOutsideClickHandler'
|
import { useOutsideClickHandler } from '../../utils/useOutsideClickHandler'
|
||||||
import { useLocalize } from '../../context/localize'
|
import { useLocalize } from '../../context/localize'
|
||||||
import { Icon } from '../_shared/Icon'
|
import { Icon } from '../_shared/Icon'
|
||||||
import { createEffect, For } from 'solid-js'
|
import { createEffect, createMemo, For, Show } from 'solid-js'
|
||||||
import { useNotifications } from '../../context/notifications'
|
import { useNotifications } from '../../context/notifications'
|
||||||
import { NotificationView } from './NotificationView'
|
import { NotificationView } from './NotificationView'
|
||||||
import { EmptyMessage } from './EmptyMessage'
|
import { EmptyMessage } from './EmptyMessage'
|
||||||
|
@ -14,6 +14,30 @@ type Props = {
|
||||||
onClose: () => void
|
onClose: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getYesterdayStart = () => {
|
||||||
|
const now = new Date()
|
||||||
|
return new Date(now.getFullYear(), now.getMonth(), now.getDate() - 1, 0, 0, 0, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
const isSameDate = (date1: Date, date2: Date) =>
|
||||||
|
date1.getDate() === date2.getDate() &&
|
||||||
|
date1.getMonth() === date2.getMonth() &&
|
||||||
|
date1.getFullYear() === date2.getFullYear()
|
||||||
|
|
||||||
|
const isToday = (date: Date) => {
|
||||||
|
return isSameDate(date, new Date())
|
||||||
|
}
|
||||||
|
|
||||||
|
const isYesterday = (date: Date) => {
|
||||||
|
const yesterday = getYesterdayStart()
|
||||||
|
return isSameDate(date, yesterday)
|
||||||
|
}
|
||||||
|
|
||||||
|
const isEarlier = (date: Date) => {
|
||||||
|
const yesterday = getYesterdayStart()
|
||||||
|
return date.getTime() < yesterday.getTime()
|
||||||
|
}
|
||||||
|
|
||||||
export const NotificationsPanel = (props: Props) => {
|
export const NotificationsPanel = (props: Props) => {
|
||||||
const { t } = useLocalize()
|
const { t } = useLocalize()
|
||||||
const { sortedNotifications } = useNotifications()
|
const { sortedNotifications } = useNotifications()
|
||||||
|
@ -55,6 +79,18 @@ export const NotificationsPanel = (props: Props) => {
|
||||||
handleHide()
|
handleHide()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const todayNotifications = createMemo(() => {
|
||||||
|
return sortedNotifications().filter((notification) => isToday(new Date(notification.createdAt)))
|
||||||
|
})
|
||||||
|
|
||||||
|
const yesterdayNotifications = createMemo(() => {
|
||||||
|
return sortedNotifications().filter((notification) => isYesterday(new Date(notification.createdAt)))
|
||||||
|
})
|
||||||
|
|
||||||
|
const earlierNotifications = createMemo(() => {
|
||||||
|
return sortedNotifications().filter((notification) => isEarlier(new Date(notification.createdAt)))
|
||||||
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
class={clsx(styles.container, {
|
class={clsx(styles.container, {
|
||||||
|
@ -67,15 +103,47 @@ export const NotificationsPanel = (props: Props) => {
|
||||||
<Icon name="close" />
|
<Icon name="close" />
|
||||||
</div>
|
</div>
|
||||||
<div class={styles.title}>{t('Notifications')}</div>
|
<div class={styles.title}>{t('Notifications')}</div>
|
||||||
<For each={sortedNotifications()} fallback={<EmptyMessage />}>
|
<Show when={sortedNotifications().length > 0} fallback={<EmptyMessage />}>
|
||||||
|
<Show when={todayNotifications().length > 0}>
|
||||||
|
<div class={styles.periodTitle}>{t('today')}</div>
|
||||||
|
<For each={todayNotifications()}>
|
||||||
{(notification) => (
|
{(notification) => (
|
||||||
<NotificationView
|
<NotificationView
|
||||||
notification={notification}
|
notification={notification}
|
||||||
class={styles.notificationView}
|
class={styles.notificationView}
|
||||||
onClick={handleNotificationViewClick}
|
onClick={handleNotificationViewClick}
|
||||||
|
dateTimeFormat={'ago'}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</For>
|
</For>
|
||||||
|
</Show>
|
||||||
|
<Show when={yesterdayNotifications().length > 0}>
|
||||||
|
<div class={styles.periodTitle}>{t('yesterday')}</div>
|
||||||
|
<For each={yesterdayNotifications()}>
|
||||||
|
{(notification) => (
|
||||||
|
<NotificationView
|
||||||
|
notification={notification}
|
||||||
|
class={styles.notificationView}
|
||||||
|
onClick={handleNotificationViewClick}
|
||||||
|
dateTimeFormat={'time'}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</For>
|
||||||
|
</Show>
|
||||||
|
<Show when={earlierNotifications().length > 0}>
|
||||||
|
<div class={styles.periodTitle}>{t('earlier')}</div>
|
||||||
|
<For each={earlierNotifications()}>
|
||||||
|
{(notification) => (
|
||||||
|
<NotificationView
|
||||||
|
notification={notification}
|
||||||
|
class={styles.notificationView}
|
||||||
|
onClick={handleNotificationViewClick}
|
||||||
|
dateTimeFormat={'date'}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</For>
|
||||||
|
</Show>
|
||||||
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
import { capitalize } from '../../utils'
|
|
||||||
import styles from './Card.module.scss'
|
|
||||||
import { createMemo, createSignal, Show } from 'solid-js'
|
import { createMemo, createSignal, Show } from 'solid-js'
|
||||||
import type { Topic } from '../../graphql/types.gen'
|
import type { Topic } from '../../graphql/types.gen'
|
||||||
import { FollowingEntity } from '../../graphql/types.gen'
|
import { FollowingEntity } from '../../graphql/types.gen'
|
||||||
|
@ -12,6 +10,9 @@ import { Icon } from '../_shared/Icon'
|
||||||
import { useLocalize } from '../../context/localize'
|
import { useLocalize } from '../../context/localize'
|
||||||
import { CardTopic } from '../Feed/CardTopic'
|
import { CardTopic } from '../Feed/CardTopic'
|
||||||
import { CheckButton } from '../_shared/CheckButton'
|
import { CheckButton } from '../_shared/CheckButton'
|
||||||
|
import { capitalize } from '../../utils/capitalize'
|
||||||
|
|
||||||
|
import styles from './Card.module.scss'
|
||||||
|
|
||||||
interface TopicProps {
|
interface TopicProps {
|
||||||
topic: Topic
|
topic: Topic
|
||||||
|
@ -109,14 +110,6 @@ export const TopicCard = (props: TopicProps) => {
|
||||||
{props.topic.body}
|
{props.topic.body}
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
<Show when={props.showDescription && !props.topic?.body && props.topic.stat?.shouts > 0}>
|
|
||||||
<div
|
|
||||||
class={clsx(styles.topicDescription)}
|
|
||||||
classList={{ [styles.topicDescriptionShort]: props.shortDescription }}
|
|
||||||
>
|
|
||||||
{props.topic.stat?.shouts} публикаций
|
|
||||||
</div>
|
|
||||||
</Show>
|
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class={styles.controlContainer}
|
class={styles.controlContainer}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
.allTopicsPage {
|
.allAuthorsPage {
|
||||||
.group {
|
.group {
|
||||||
@include font-size(1.6rem);
|
@include font-size(1.6rem);
|
||||||
|
|
||||||
|
@ -32,10 +32,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.stats {
|
|
||||||
margin-top: 2.4rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loadMoreContainer {
|
.loadMoreContainer {
|
||||||
margin-top: 48px;
|
margin-top: 48px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
@ -52,6 +48,7 @@
|
||||||
|
|
||||||
.alphabet {
|
.alphabet {
|
||||||
@include font-size(1.5rem);
|
@include font-size(1.5rem);
|
||||||
|
|
||||||
color: rgba(0 0 0 / 20%);
|
color: rgba(0 0 0 / 20%);
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
@ -71,6 +68,7 @@
|
||||||
|
|
||||||
.articlesCounter {
|
.articlesCounter {
|
||||||
@include font-size(1.2rem);
|
@include font-size(1.2rem);
|
||||||
|
|
||||||
margin-left: 0.5em;
|
margin-left: 0.5em;
|
||||||
vertical-align: super;
|
vertical-align: super;
|
||||||
}
|
}
|
|
@ -6,12 +6,13 @@ import { useRouter } from '../../stores/router'
|
||||||
import { AuthorCard } from '../Author/AuthorCard'
|
import { AuthorCard } from '../Author/AuthorCard'
|
||||||
import { clsx } from 'clsx'
|
import { clsx } from 'clsx'
|
||||||
import { useSession } from '../../context/session'
|
import { useSession } from '../../context/session'
|
||||||
import styles from '../../styles/AllTopics.module.scss'
|
|
||||||
import { SearchField } from '../_shared/SearchField'
|
import { SearchField } from '../_shared/SearchField'
|
||||||
import { scrollHandler } from '../../utils/scroll'
|
import { scrollHandler } from '../../utils/scroll'
|
||||||
import { useLocalize } from '../../context/localize'
|
import { useLocalize } from '../../context/localize'
|
||||||
import { dummyFilter } from '../../utils/dummyFilter'
|
import { dummyFilter } from '../../utils/dummyFilter'
|
||||||
|
|
||||||
|
import styles from './AllAuthors.module.scss'
|
||||||
|
|
||||||
type AllAuthorsPageSearchParams = {
|
type AllAuthorsPageSearchParams = {
|
||||||
by: '' | 'name' | 'shouts' | 'followers'
|
by: '' | 'name' | 'shouts' | 'followers'
|
||||||
}
|
}
|
||||||
|
@ -109,7 +110,7 @@ export const AllAuthorsView = (props: AllAuthorsViewProps) => {
|
||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class={clsx(styles.allTopicsPage, 'wide-container')}>
|
<div class={clsx(styles.allAuthorsPage, 'wide-container')}>
|
||||||
<Show when={sortedAuthors().length > 0}>
|
<Show when={sortedAuthors().length > 0}>
|
||||||
<div class="offset-md-5">
|
<div class="offset-md-5">
|
||||||
<AllAuthorsHead />
|
<AllAuthorsHead />
|
||||||
|
|
115
src/components/Views/AllTopics.module.scss
Normal file
115
src/components/Views/AllTopics.module.scss
Normal file
|
@ -0,0 +1,115 @@
|
||||||
|
.allTopicsPage {
|
||||||
|
.group {
|
||||||
|
@include font-size(1.6rem);
|
||||||
|
|
||||||
|
margin: 3em 0 9.6rem;
|
||||||
|
|
||||||
|
@include media-breakpoint-down(sm) {
|
||||||
|
margin-bottom: 6.4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
margin-bottom: 3.2rem;
|
||||||
|
text-transform: capitalize;
|
||||||
|
|
||||||
|
@include media-breakpoint-down(sm) {
|
||||||
|
margin-bottom: 1.6rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.topic {
|
||||||
|
margin-bottom: 2.4rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
width: auto;
|
||||||
|
|
||||||
|
.search-input {
|
||||||
|
display: inline-block;
|
||||||
|
width: 100px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.stats {
|
||||||
|
@include font-size(1.7rem);
|
||||||
|
|
||||||
|
color: #9fa1a7;
|
||||||
|
display: flex;
|
||||||
|
margin: 0 0 1em;
|
||||||
|
|
||||||
|
@include media-breakpoint-down(md) {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include media-breakpoint-down(sm) {
|
||||||
|
margin-top: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.statsItem {
|
||||||
|
@include font-size(1.5rem);
|
||||||
|
|
||||||
|
margin-right: 1.6rem;
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.compact {
|
||||||
|
font-size: small;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.followers {
|
||||||
|
word-break: keep-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.button {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.loadMoreContainer {
|
||||||
|
margin-top: 48px;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
.loadMoreButton {
|
||||||
|
padding: 0.6em 3em;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
@include media-breakpoint-up(sm) {
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.alphabet {
|
||||||
|
@include font-size(1.5rem);
|
||||||
|
color: rgba(0 0 0 / 20%);
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
font-weight: 700;
|
||||||
|
margin: 1.5em -3% 0 0;
|
||||||
|
|
||||||
|
li {
|
||||||
|
min-width: 1.5em;
|
||||||
|
margin-right: 3%;
|
||||||
|
color: rgb(0 0 0 / 30%);
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.articlesCounter {
|
||||||
|
@include font-size(1.2rem);
|
||||||
|
margin-left: 0.5em;
|
||||||
|
vertical-align: super;
|
||||||
|
}
|
||||||
|
|
||||||
|
.viewSwitcher {
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
|
@ -6,13 +6,13 @@ import { useRouter } from '../../stores/router'
|
||||||
import { TopicCard } from '../Topic/Card'
|
import { TopicCard } from '../Topic/Card'
|
||||||
import { clsx } from 'clsx'
|
import { clsx } from 'clsx'
|
||||||
import { useSession } from '../../context/session'
|
import { useSession } from '../../context/session'
|
||||||
import styles from '../../styles/AllTopics.module.scss'
|
|
||||||
import { SearchField } from '../_shared/SearchField'
|
import { SearchField } from '../_shared/SearchField'
|
||||||
import { scrollHandler } from '../../utils/scroll'
|
import { scrollHandler } from '../../utils/scroll'
|
||||||
import { StatMetrics } from '../_shared/StatMetrics'
|
|
||||||
import { useLocalize } from '../../context/localize'
|
import { useLocalize } from '../../context/localize'
|
||||||
import { dummyFilter } from '../../utils/dummyFilter'
|
import { dummyFilter } from '../../utils/dummyFilter'
|
||||||
|
|
||||||
|
import styles from './AllTopics.module.scss'
|
||||||
|
|
||||||
type AllTopicsPageSearchParams = {
|
type AllTopicsPageSearchParams = {
|
||||||
by: 'shouts' | 'authors' | 'title' | ''
|
by: 'shouts' | 'authors' | 'title' | ''
|
||||||
}
|
}
|
||||||
|
@ -168,7 +168,17 @@ export const AllTopicsView = (props: AllTopicsViewProps) => {
|
||||||
showPublications={true}
|
showPublications={true}
|
||||||
showDescription={true}
|
showDescription={true}
|
||||||
/>
|
/>
|
||||||
<StatMetrics fields={['shouts', 'authors', 'followers']} stat={topic.stat} />
|
<div class={styles.stats}>
|
||||||
|
<span class={styles.statsItem}>
|
||||||
|
{t('shoutsWithCount', { count: topic.stat.shouts })}
|
||||||
|
</span>
|
||||||
|
<span class={styles.statsItem}>
|
||||||
|
{t('authorsWithCount', { count: topic.stat.authors })}
|
||||||
|
</span>
|
||||||
|
<span class={styles.statsItem}>
|
||||||
|
{t('followersWithCount', { count: topic.stat.followers })}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</For>
|
</For>
|
||||||
|
|
|
@ -1,36 +0,0 @@
|
||||||
.statMetrics {
|
|
||||||
@include font-size(1.7rem);
|
|
||||||
color: #9fa1a7;
|
|
||||||
display: flex;
|
|
||||||
margin: 0 0 1em;
|
|
||||||
|
|
||||||
@include media-breakpoint-down(md) {
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
@include media-breakpoint-down(sm) {
|
|
||||||
margin-top: 0.5em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.statMetricsItem {
|
|
||||||
@include font-size(1.5rem);
|
|
||||||
margin-right: 1.6rem;
|
|
||||||
white-space: nowrap;
|
|
||||||
|
|
||||||
&:last-child {
|
|
||||||
margin-right: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.compact {
|
|
||||||
font-size: small;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.followers {
|
|
||||||
word-break: keep-all;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.button {
|
|
||||||
float: right;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,45 +0,0 @@
|
||||||
import { For } from 'solid-js'
|
|
||||||
import type { Stat, TopicStat } from '../../graphql/types.gen'
|
|
||||||
import { plural } from '../../utils'
|
|
||||||
import styles from './Stat.module.scss'
|
|
||||||
import { useLocalize } from '../../context/localize'
|
|
||||||
|
|
||||||
interface StatMetricsProps {
|
|
||||||
fields?: string[]
|
|
||||||
stat: Stat | TopicStat
|
|
||||||
compact?: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
const pseudonames = {
|
|
||||||
// topics: 'topics' # amount of topics for community💥
|
|
||||||
followed: 'follower',
|
|
||||||
followers: 'follower',
|
|
||||||
rating: 'like',
|
|
||||||
viewed: 'view',
|
|
||||||
views: 'view',
|
|
||||||
reacted: 'involving',
|
|
||||||
reactions: 'involving',
|
|
||||||
commented: 'discussion',
|
|
||||||
comments: 'discussion',
|
|
||||||
shouts: 'post',
|
|
||||||
authors: 'author'
|
|
||||||
}
|
|
||||||
|
|
||||||
export const StatMetrics = (props: StatMetricsProps) => {
|
|
||||||
const { t, lang } = useLocalize()
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div class={styles.statMetrics}>
|
|
||||||
<For each={props.fields}>
|
|
||||||
{(entity: string) => (
|
|
||||||
<span class={styles.statMetricsItem} classList={{ compact: props.compact }}>
|
|
||||||
{props.stat[entity] +
|
|
||||||
' ' +
|
|
||||||
t(pseudonames[entity] || entity.slice(-1)) +
|
|
||||||
plural(props.stat[entity] || 0, lang() === 'ru' ? ['ов', '', 'а'] : ['s', '', 's'])}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</For>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
3
src/components/_shared/TimeAgo/TimeAgo.module.scss
Normal file
3
src/components/_shared/TimeAgo/TimeAgo.module.scss
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
.TimeAgo {
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
37
src/components/_shared/TimeAgo/TimeAgo.tsx
Normal file
37
src/components/_shared/TimeAgo/TimeAgo.tsx
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
import { clsx } from 'clsx'
|
||||||
|
import { useLocalize } from '../../../context/localize'
|
||||||
|
import { createSignal, onCleanup, onMount } from 'solid-js'
|
||||||
|
|
||||||
|
import styles from './TimeAgo.module.scss'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
date: any
|
||||||
|
class?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const TimeAgo = (props: Props) => {
|
||||||
|
const { formatDate, formatTimeAgo } = useLocalize()
|
||||||
|
const [formattedTimeAgo, setFormattedTimeAgo] = createSignal(formatTimeAgo(new Date(props.date)))
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
let timerId: NodeJS.Timeout
|
||||||
|
const updateTimeAgo = () => {
|
||||||
|
timerId = setTimeout(() => {
|
||||||
|
setFormattedTimeAgo(formatTimeAgo(new Date(props.date)))
|
||||||
|
updateTimeAgo()
|
||||||
|
}, 1000)
|
||||||
|
}
|
||||||
|
updateTimeAgo()
|
||||||
|
|
||||||
|
onCleanup(() => clearTimeout(timerId))
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
class={clsx(styles.TimeAgo, props.class)}
|
||||||
|
title={formatDate(new Date(props.date), { month: '2-digit', hour: '2-digit', minute: '2-digit' })}
|
||||||
|
>
|
||||||
|
{formattedTimeAgo()}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
1
src/components/_shared/TimeAgo/index.ts
Normal file
1
src/components/_shared/TimeAgo/index.ts
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export { TimeAgo } from './TimeAgo'
|
|
@ -1,14 +1,23 @@
|
||||||
import type { i18n } from 'i18next'
|
import type { i18n } from 'i18next'
|
||||||
import type { Accessor, JSX } from 'solid-js'
|
import type { Accessor, JSX } from 'solid-js'
|
||||||
import { createContext, createEffect, createSignal, Show, useContext } from 'solid-js'
|
import { createContext, createEffect, createMemo, createSignal, Show, useContext } from 'solid-js'
|
||||||
import { useRouter } from '../stores/router'
|
import { useRouter } from '../stores/router'
|
||||||
import i18next, { changeLanguage, t } from 'i18next'
|
import i18next, { changeLanguage, t } from 'i18next'
|
||||||
import Cookie from 'js-cookie'
|
import Cookie from 'js-cookie'
|
||||||
|
import TimeAgo from 'javascript-time-ago'
|
||||||
|
import en from 'javascript-time-ago/locale/en'
|
||||||
|
import ru from 'javascript-time-ago/locale/ru'
|
||||||
|
|
||||||
|
TimeAgo.addLocale(en)
|
||||||
|
TimeAgo.addLocale(ru)
|
||||||
|
|
||||||
type LocalizeContextType = {
|
type LocalizeContextType = {
|
||||||
t: i18n['t']
|
t: i18n['t']
|
||||||
lang: Accessor<Language>
|
lang: Accessor<Language>
|
||||||
setLang: (lang: Language) => void
|
setLang: (lang: Language) => void
|
||||||
|
formatTime: (date: Date, options?: Intl.DateTimeFormatOptions) => string
|
||||||
|
formatDate: (date: Date, options?: Intl.DateTimeFormatOptions) => string
|
||||||
|
formatTimeAgo: (date: Date) => string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Language = 'ru' | 'en'
|
export type Language = 'ru' | 'en'
|
||||||
|
@ -21,7 +30,9 @@ export function useLocalize() {
|
||||||
|
|
||||||
export const LocalizeProvider = (props: { children: JSX.Element }) => {
|
export const LocalizeProvider = (props: { children: JSX.Element }) => {
|
||||||
const [lang, setLang] = createSignal<Language>(i18next.language === 'en' ? 'en' : 'ru')
|
const [lang, setLang] = createSignal<Language>(i18next.language === 'en' ? 'en' : 'ru')
|
||||||
const { searchParams, changeSearchParam } = useRouter<{ lng: string }>()
|
const { searchParams, changeSearchParam } = useRouter<{
|
||||||
|
lng: string
|
||||||
|
}>()
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
if (!searchParams().lng) {
|
if (!searchParams().lng) {
|
||||||
|
@ -36,7 +47,43 @@ export const LocalizeProvider = (props: { children: JSX.Element }) => {
|
||||||
changeSearchParam({ lng: null }, true)
|
changeSearchParam({ lng: null }, true)
|
||||||
})
|
})
|
||||||
|
|
||||||
const value: LocalizeContextType = { t, lang, setLang }
|
const formatTime = (date: Date, options: Intl.DateTimeFormatOptions = {}) => {
|
||||||
|
const opts = Object.assign(
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit'
|
||||||
|
},
|
||||||
|
options
|
||||||
|
)
|
||||||
|
|
||||||
|
return date.toLocaleTimeString(lang(), opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
const formatDate = (date: Date, options: Intl.DateTimeFormatOptions = {}) => {
|
||||||
|
const opts = Object.assign(
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
month: 'long',
|
||||||
|
day: 'numeric',
|
||||||
|
year: 'numeric'
|
||||||
|
},
|
||||||
|
options
|
||||||
|
)
|
||||||
|
|
||||||
|
let result = date.toLocaleDateString(lang(), opts)
|
||||||
|
if (lang() === 'ru') {
|
||||||
|
result = result.replace(' г.', '')
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
const timeAgo = createMemo(() => new TimeAgo(lang()))
|
||||||
|
|
||||||
|
const formatTimeAgo = (date: Date) => timeAgo().format(date)
|
||||||
|
|
||||||
|
const value: LocalizeContextType = { t, lang, setLang, formatTime, formatDate, formatTimeAgo }
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<LocalizeContext.Provider value={value}>
|
<LocalizeContext.Provider value={value}>
|
||||||
|
|
|
@ -95,7 +95,7 @@ export const NotificationsProvider = (props: { children: JSX.Element }) => {
|
||||||
loadNotifications()
|
loadNotifications()
|
||||||
|
|
||||||
const token = getToken()
|
const token = getToken()
|
||||||
const eventSource = new EventSource(`https://chat.discours.io/connect/?token=${token}`)
|
const eventSource = new EventSource(`https://connect.discours.io/${token}`)
|
||||||
|
|
||||||
eventSource.onmessage = (event) => {
|
eventSource.onmessage = (event) => {
|
||||||
console.log('[context.notifications] Received event:', event)
|
console.log('[context.notifications] Received event:', event)
|
||||||
|
|
|
@ -4,10 +4,11 @@ import { App } from '../components/App'
|
||||||
import { initRouter } from '../stores/router'
|
import { initRouter } from '../stores/router'
|
||||||
import type { PageContext } from './types'
|
import type { PageContext } from './types'
|
||||||
import { MetaProvider, renderTags } from '@solidjs/meta'
|
import { MetaProvider, renderTags } from '@solidjs/meta'
|
||||||
import i18next, { changeLanguage, init as initI18next } from 'i18next'
|
import i18next from 'i18next'
|
||||||
import ru from '../../public/locales/ru/translation.json'
|
import ru from '../../public/locales/ru/translation.json'
|
||||||
import en from '../../public/locales/en/translation.json'
|
import en from '../../public/locales/en/translation.json'
|
||||||
import type { Language } from '../context/localize'
|
import type { Language } from '../context/localize'
|
||||||
|
import ICU from 'i18next-icu'
|
||||||
|
|
||||||
export const passToClient = ['pageProps', 'lng', 'documentProps', 'is404']
|
export const passToClient = ['pageProps', 'lng', 'documentProps', 'is404']
|
||||||
|
|
||||||
|
@ -32,7 +33,7 @@ export const render = async (pageContext: PageContext) => {
|
||||||
|
|
||||||
if (!i18next.isInitialized) {
|
if (!i18next.isInitialized) {
|
||||||
// eslint-disable-next-line import/no-named-as-default-member
|
// eslint-disable-next-line import/no-named-as-default-member
|
||||||
await initI18next({
|
await i18next.use(ICU).init({
|
||||||
// debug: true,
|
// debug: true,
|
||||||
supportedLngs: ['ru', 'en'],
|
supportedLngs: ['ru', 'en'],
|
||||||
fallbackLng: lng,
|
fallbackLng: lng,
|
||||||
|
@ -44,7 +45,7 @@ export const render = async (pageContext: PageContext) => {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
} else if (i18next.language !== lng) {
|
} else if (i18next.language !== lng) {
|
||||||
await changeLanguage(lng)
|
await i18next.changeLanguage(lng)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pageContext.is404) {
|
if (pageContext.is404) {
|
||||||
|
|
|
@ -357,7 +357,7 @@ export const apiClient = {
|
||||||
/*
|
/*
|
||||||
getNotifications: async (params: NotificationsQueryParams): Promise<NotificationsQueryResult> => {
|
getNotifications: async (params: NotificationsQueryParams): Promise<NotificationsQueryResult> => {
|
||||||
const resp = await privateGraphQLClient.query(notifications, params).toPromise()
|
const resp = await privateGraphQLClient.query(notifications, params).toPromise()
|
||||||
console.debug(resp.data)
|
// console.debug(resp.data)
|
||||||
return resp.data.loadNotifications
|
return resp.data.loadNotifications
|
||||||
},
|
},
|
||||||
markNotificationAsRead: async (notificationId: number): Promise<void> => {
|
markNotificationAsRead: async (notificationId: number): Promise<void> => {
|
||||||
|
|
9
src/utils/capitalize.ts
Normal file
9
src/utils/capitalize.ts
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
export const capitalize = (originalString: string, firstonly = false) => {
|
||||||
|
const s = originalString.trim()
|
||||||
|
return firstonly
|
||||||
|
? s.charAt(0).toUpperCase() + s.slice(1)
|
||||||
|
: s
|
||||||
|
.split(' ')
|
||||||
|
.map((w) => w.charAt(0).toUpperCase() + w.slice(1))
|
||||||
|
.join(' ')
|
||||||
|
}
|
|
@ -1,7 +1,6 @@
|
||||||
import { translit } from './ru2en'
|
import { translit } from './ru2en'
|
||||||
import { Author, Topic } from '../graphql/types.gen'
|
import { Author, Topic } from '../graphql/types.gen'
|
||||||
|
import { isAuthor } from './isAuthor'
|
||||||
type SearchData = Array<Author | Topic>
|
|
||||||
|
|
||||||
const prepareQuery = (searchQuery, lang) => {
|
const prepareQuery = (searchQuery, lang) => {
|
||||||
const q = searchQuery.toLowerCase()
|
const q = searchQuery.toLowerCase()
|
||||||
|
@ -14,9 +13,16 @@ const stringMatches = (str, q, lang) => {
|
||||||
return preparedStr.split(' ').some((word) => word.startsWith(q))
|
return preparedStr.split(' ').some((word) => word.startsWith(q))
|
||||||
}
|
}
|
||||||
|
|
||||||
export const dummyFilter = (data: SearchData, searchQuery: string, lang: 'ru' | 'en'): SearchData => {
|
export const dummyFilter = <T extends Topic | Author>(
|
||||||
|
data: T[],
|
||||||
|
searchQuery: string,
|
||||||
|
lang: 'ru' | 'en'
|
||||||
|
): T[] => {
|
||||||
const q = prepareQuery(searchQuery, lang)
|
const q = prepareQuery(searchQuery, lang)
|
||||||
if (q.length === 0) return data
|
|
||||||
|
if (q.length === 0) {
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
return data.filter((item) => {
|
return data.filter((item) => {
|
||||||
const slugMatches = item.slug && item.slug.split('-').some((w) => w.startsWith(q))
|
const slugMatches = item.slug && item.slug.split('-').some((w) => w.startsWith(q))
|
||||||
|
@ -26,9 +32,10 @@ export const dummyFilter = (data: SearchData, searchQuery: string, lang: 'ru' |
|
||||||
return stringMatches(item.title, q, lang)
|
return stringMatches(item.title, q, lang)
|
||||||
}
|
}
|
||||||
|
|
||||||
if ('name' in item) {
|
if (isAuthor(item)) {
|
||||||
return stringMatches(item.name, q, lang) || (item.bio && stringMatches(item.bio, q, lang))
|
return stringMatches(item.name, q, lang) || (item.bio && stringMatches(item.bio, q, lang))
|
||||||
}
|
}
|
||||||
|
|
||||||
// If it does not match any of the 'slug', 'title', 'name' , 'bio' fields
|
// If it does not match any of the 'slug', 'title', 'name' , 'bio' fields
|
||||||
// current element should not be included in the filtered array
|
// current element should not be included in the filtered array
|
||||||
return false
|
return false
|
||||||
|
|
|
@ -1,17 +0,0 @@
|
||||||
import { Accessor, createMemo } from 'solid-js'
|
|
||||||
import { useLocalize } from '../context/localize'
|
|
||||||
|
|
||||||
// unix timestamp in seconds
|
|
||||||
const formattedTime = (time: number): Accessor<string> => {
|
|
||||||
// FIXME: maybe it's better to move it from here
|
|
||||||
const { lang } = useLocalize()
|
|
||||||
|
|
||||||
return createMemo<string>(() => {
|
|
||||||
return new Date(time).toLocaleTimeString(lang(), {
|
|
||||||
hour: 'numeric',
|
|
||||||
minute: 'numeric'
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export default formattedTime
|
|
|
@ -1,83 +0,0 @@
|
||||||
export const reflow = () => document.body.clientWidth
|
|
||||||
|
|
||||||
export const unique = (v) => {
|
|
||||||
const s = new Set(v)
|
|
||||||
return [...s]
|
|
||||||
}
|
|
||||||
|
|
||||||
export const preventSmoothScrollOnTabbing = () => {
|
|
||||||
document.addEventListener('keydown', (e) => {
|
|
||||||
if (e.key !== 'Tab') return
|
|
||||||
|
|
||||||
document.documentElement.style.scrollBehavior = ''
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
document.documentElement.style.scrollBehavior = 'smooth'
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export const capitalize = (originalString: string, firstonly = false) => {
|
|
||||||
const s = originalString.trim()
|
|
||||||
return firstonly
|
|
||||||
? s.charAt(0).toUpperCase() + s.slice(1)
|
|
||||||
: s
|
|
||||||
.split(' ')
|
|
||||||
.map((w) => w.charAt(0).toUpperCase() + w.slice(1))
|
|
||||||
.join(' ')
|
|
||||||
}
|
|
||||||
|
|
||||||
export const plural = (amount: number, w: string[]) => {
|
|
||||||
try {
|
|
||||||
const a = amount.toString()
|
|
||||||
const x = Number.parseInt(a.at(-1))
|
|
||||||
const xx = Number.parseInt(a.at(-2) + a.at(-1))
|
|
||||||
|
|
||||||
if (xx > 5 && xx < 20) return w[0]
|
|
||||||
|
|
||||||
if (x === 1) return w[1]
|
|
||||||
|
|
||||||
if (x > 1 && x < 5) return w[2]
|
|
||||||
} catch (error) {
|
|
||||||
console.error('[utils] plural error', error)
|
|
||||||
}
|
|
||||||
|
|
||||||
return w[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
export const shuffle = (items: any[]) => {
|
|
||||||
const cached = [...items]
|
|
||||||
let temp
|
|
||||||
let i = cached.length
|
|
||||||
let rand
|
|
||||||
|
|
||||||
while (--i) {
|
|
||||||
rand = Math.floor(i * Math.random())
|
|
||||||
temp = cached[rand]
|
|
||||||
cached[rand] = cached[i]
|
|
||||||
cached[i] = temp
|
|
||||||
}
|
|
||||||
|
|
||||||
return cached
|
|
||||||
}
|
|
||||||
|
|
||||||
export const snake2camel = (s: string) =>
|
|
||||||
s
|
|
||||||
.split(/(?=[A-Z])/)
|
|
||||||
.join('-')
|
|
||||||
.toLowerCase()
|
|
||||||
|
|
||||||
export const formatDate = (date: Date, options: Intl.DateTimeFormatOptions = {}) => {
|
|
||||||
const opts = Object.assign(
|
|
||||||
{},
|
|
||||||
{
|
|
||||||
month: 'long',
|
|
||||||
day: 'numeric',
|
|
||||||
year: 'numeric'
|
|
||||||
},
|
|
||||||
options
|
|
||||||
)
|
|
||||||
|
|
||||||
return date.toLocaleDateString('ru', opts).replace(' г.', '')
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user