aboutsummaryrefslogtreecommitdiff
path: root/FrontEnd
diff options
context:
space:
mode:
Diffstat (limited to 'FrontEnd')
-rw-r--r--FrontEnd/package-lock.json81
-rw-r--r--FrontEnd/package.json8
-rw-r--r--FrontEnd/src/app/utilities/useReverseScrollPositionRemember.ts45
-rw-r--r--FrontEnd/src/app/utilities/useScrollToTop.ts49
-rw-r--r--FrontEnd/src/app/views/timeline-common/TimelinePagedPostListView.tsx16
-rw-r--r--FrontEnd/src/app/views/timeline-common/TimelinePostView.tsx11
6 files changed, 133 insertions, 77 deletions
diff --git a/FrontEnd/package-lock.json b/FrontEnd/package-lock.json
index a86b91f0..664eff6d 100644
--- a/FrontEnd/package-lock.json
+++ b/FrontEnd/package-lock.json
@@ -21,7 +21,7 @@
"lodash": "^4.17.21",
"pepjs": "^0.5.3",
"react": "^17.0.1",
- "react-bootstrap": "^2.0.0-alpha.2",
+ "react-bootstrap": "^2.0.0-beta.0",
"react-color": "^2.19.3",
"react-dom": "^17.0.2",
"react-i18next": "^11.10.0",
@@ -44,7 +44,7 @@
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.0-beta.9",
"@types/color": "^3.0.1",
"@types/lodash": "^4.14.170",
- "@types/node": "^15.6.1",
+ "@types/node": "^15.9.0",
"@types/react": "^17.0.9",
"@types/react-color": "^3.0.4",
"@types/react-dom": "^17.0.6",
@@ -52,7 +52,7 @@
"@types/react-router": "^5.1.13",
"@types/react-router-bootstrap": "^0.24.5",
"@types/react-router-dom": "^5.1.7",
- "@types/remarkable": "^2.0.1",
+ "@types/remarkable": "^2.0.2",
"@types/webpack-env": "^1.16.0",
"@types/xregexp": "^4.4.0",
"@typescript-eslint/eslint-plugin": "^4.26.0",
@@ -1731,15 +1731,6 @@
"integrity": "sha512-/+CRPXpBDpo2RK9C68N3b2cOvO0Cf5B9aPijHsoDQTHivnGSObdOF2BRQOYjojWTDy6nQvMjmqRXIxH55VjxxA==",
"dev": true
},
- "node_modules/@types/classnames": {
- "version": "2.3.1",
- "resolved": "https://registry.npmjs.org/@types/classnames/-/classnames-2.3.1.tgz",
- "integrity": "sha512-zeOWb0JGBoVmlQoznvqXbE0tEC/HONsnoUNH19Hc96NFsTAwTXbTqb8FMYkru1F/iqp7a18Ws3nWJvtA1sHD1A==",
- "deprecated": "This is a stub types definition. classnames provides its own type definitions, so you do not need this installed.",
- "dependencies": {
- "classnames": "*"
- }
- },
"node_modules/@types/color": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/@types/color/-/color-3.0.1.tgz",
@@ -1851,9 +1842,9 @@
"dev": true
},
"node_modules/@types/node": {
- "version": "15.6.1",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-15.6.1.tgz",
- "integrity": "sha512-7EIraBEyRHEe7CH+Fm1XvgqU6uwZN8Q7jppJGcqjROMT29qhAuuOxYB1uEY5UMYQKEmA5D+5tBnhdaPXSsLONA==",
+ "version": "15.9.0",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-15.9.0.tgz",
+ "integrity": "sha512-AR1Vq1Ei1GaA5FjKL5PBqblTZsL5M+monvGSZwe6sSIdGiuu7Xr/pNwWJY+0ZQuN8AapD/XMB5IzBAyYRFbocA==",
"dev": true
},
"node_modules/@types/parse-json": {
@@ -1954,9 +1945,9 @@
}
},
"node_modules/@types/remarkable": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/@types/remarkable/-/remarkable-2.0.1.tgz",
- "integrity": "sha512-4KTpW8aVjVkTgaWlEOAuorBAdlUrvlzzPlVrt9vsAAM6nB/nUln7j0bcIRLdUH0Sf2g6ZPVBfibm4+8y1glFwQ==",
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/@types/remarkable/-/remarkable-2.0.2.tgz",
+ "integrity": "sha512-z5JSTrZfEluIlTLWC9jiDojfWReaKpRDRj0pHSgNPlmhe5H0V/hJZIb8zeGxq0Zc0lDMKU1Rhr6cjkeabP7a6Q==",
"dev": true,
"dependencies": {
"@types/highlight.js": "^9.7.0",
@@ -9310,25 +9301,24 @@
}
},
"node_modules/react-bootstrap": {
- "version": "2.0.0-alpha.2",
- "resolved": "https://registry.npmjs.org/react-bootstrap/-/react-bootstrap-2.0.0-alpha.2.tgz",
- "integrity": "sha512-Sjaq/EwI0ZJaN+yja4FLeJczvXuoYDxwNQsJsuzdpTAFCtCvTAh0P/PUA8n1CTxePfjbPzcnSLhKGN0k0FGdAg==",
+ "version": "2.0.0-beta.0",
+ "resolved": "https://registry.npmjs.org/react-bootstrap/-/react-bootstrap-2.0.0-beta.0.tgz",
+ "integrity": "sha512-9grXyFdznG7r7HT7etkiKHkgqVVgasDFjkTtNCAx9xAQllAjKl6vuBGBVc3F+KLNpdTzrxrx5RWNIgd8ZpascA==",
"dependencies": {
- "@babel/runtime": "^7.13.8",
+ "@babel/runtime": "^7.14.0",
"@restart/context": "^2.1.4",
"@restart/hooks": "^0.3.26",
- "@types/classnames": "^2.2.10",
"@types/invariant": "^2.2.33",
"@types/prop-types": "^15.7.3",
- "@types/react": ">=16.14.4",
+ "@types/react": ">=16.14.8",
"@types/react-transition-group": "^4.4.1",
"@types/warning": "^3.0.0",
- "classnames": "^2.2.6",
- "dom-helpers": "^5.1.2",
+ "classnames": "^2.3.1",
+ "dom-helpers": "^5.2.1",
"invariant": "^2.2.4",
"prop-types": "^15.7.2",
"prop-types-extra": "^1.1.0",
- "react-overlays": "^5.0.0",
+ "react-overlays": "^5.0.1",
"react-transition-group": "^4.4.1",
"uncontrollable": "^7.2.1",
"warning": "^4.0.3"
@@ -13357,14 +13347,6 @@
"integrity": "sha512-/+CRPXpBDpo2RK9C68N3b2cOvO0Cf5B9aPijHsoDQTHivnGSObdOF2BRQOYjojWTDy6nQvMjmqRXIxH55VjxxA==",
"dev": true
},
- "@types/classnames": {
- "version": "2.3.1",
- "resolved": "https://registry.npmjs.org/@types/classnames/-/classnames-2.3.1.tgz",
- "integrity": "sha512-zeOWb0JGBoVmlQoznvqXbE0tEC/HONsnoUNH19Hc96NFsTAwTXbTqb8FMYkru1F/iqp7a18Ws3nWJvtA1sHD1A==",
- "requires": {
- "classnames": "*"
- }
- },
"@types/color": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/@types/color/-/color-3.0.1.tgz",
@@ -13476,9 +13458,9 @@
"dev": true
},
"@types/node": {
- "version": "15.6.1",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-15.6.1.tgz",
- "integrity": "sha512-7EIraBEyRHEe7CH+Fm1XvgqU6uwZN8Q7jppJGcqjROMT29qhAuuOxYB1uEY5UMYQKEmA5D+5tBnhdaPXSsLONA==",
+ "version": "15.9.0",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-15.9.0.tgz",
+ "integrity": "sha512-AR1Vq1Ei1GaA5FjKL5PBqblTZsL5M+monvGSZwe6sSIdGiuu7Xr/pNwWJY+0ZQuN8AapD/XMB5IzBAyYRFbocA==",
"dev": true
},
"@types/parse-json": {
@@ -13579,9 +13561,9 @@
}
},
"@types/remarkable": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/@types/remarkable/-/remarkable-2.0.1.tgz",
- "integrity": "sha512-4KTpW8aVjVkTgaWlEOAuorBAdlUrvlzzPlVrt9vsAAM6nB/nUln7j0bcIRLdUH0Sf2g6ZPVBfibm4+8y1glFwQ==",
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/@types/remarkable/-/remarkable-2.0.2.tgz",
+ "integrity": "sha512-z5JSTrZfEluIlTLWC9jiDojfWReaKpRDRj0pHSgNPlmhe5H0V/hJZIb8zeGxq0Zc0lDMKU1Rhr6cjkeabP7a6Q==",
"dev": true,
"requires": {
"@types/highlight.js": "^9.7.0",
@@ -19174,25 +19156,24 @@
}
},
"react-bootstrap": {
- "version": "2.0.0-alpha.2",
- "resolved": "https://registry.npmjs.org/react-bootstrap/-/react-bootstrap-2.0.0-alpha.2.tgz",
- "integrity": "sha512-Sjaq/EwI0ZJaN+yja4FLeJczvXuoYDxwNQsJsuzdpTAFCtCvTAh0P/PUA8n1CTxePfjbPzcnSLhKGN0k0FGdAg==",
+ "version": "2.0.0-beta.0",
+ "resolved": "https://registry.npmjs.org/react-bootstrap/-/react-bootstrap-2.0.0-beta.0.tgz",
+ "integrity": "sha512-9grXyFdznG7r7HT7etkiKHkgqVVgasDFjkTtNCAx9xAQllAjKl6vuBGBVc3F+KLNpdTzrxrx5RWNIgd8ZpascA==",
"requires": {
- "@babel/runtime": "^7.13.8",
+ "@babel/runtime": "^7.14.0",
"@restart/context": "^2.1.4",
"@restart/hooks": "^0.3.26",
- "@types/classnames": "^2.2.10",
"@types/invariant": "^2.2.33",
"@types/prop-types": "^15.7.3",
- "@types/react": ">=16.14.4",
+ "@types/react": ">=16.14.8",
"@types/react-transition-group": "^4.4.1",
"@types/warning": "^3.0.0",
- "classnames": "^2.2.6",
- "dom-helpers": "^5.1.2",
+ "classnames": "^2.3.1",
+ "dom-helpers": "^5.2.1",
"invariant": "^2.2.4",
"prop-types": "^15.7.2",
"prop-types-extra": "^1.1.0",
- "react-overlays": "^5.0.0",
+ "react-overlays": "^5.0.1",
"react-transition-group": "^4.4.1",
"uncontrollable": "^7.2.1",
"warning": "^4.0.3"
diff --git a/FrontEnd/package.json b/FrontEnd/package.json
index ef476709..f00a86e7 100644
--- a/FrontEnd/package.json
+++ b/FrontEnd/package.json
@@ -1,6 +1,6 @@
{
"name": "timeline",
- "version": "0.3.0",
+ "version": "0.4.0",
"private": true,
"homepage": "https://timeline.crupest.life",
"keywords": [],
@@ -19,7 +19,7 @@
"lodash": "^4.17.21",
"pepjs": "^0.5.3",
"react": "^17.0.1",
- "react-bootstrap": "^2.0.0-alpha.2",
+ "react-bootstrap": "^2.0.0-beta.0",
"react-color": "^2.19.3",
"react-dom": "^17.0.2",
"react-i18next": "^11.10.0",
@@ -60,7 +60,7 @@
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.0-beta.9",
"@types/color": "^3.0.1",
"@types/lodash": "^4.14.170",
- "@types/node": "^15.6.1",
+ "@types/node": "^15.9.0",
"@types/react": "^17.0.9",
"@types/react-color": "^3.0.4",
"@types/react-dom": "^17.0.6",
@@ -68,7 +68,7 @@
"@types/react-router": "^5.1.13",
"@types/react-router-bootstrap": "^0.24.5",
"@types/react-router-dom": "^5.1.7",
- "@types/remarkable": "^2.0.1",
+ "@types/remarkable": "^2.0.2",
"@types/webpack-env": "^1.16.0",
"@types/xregexp": "^4.4.0",
"@typescript-eslint/eslint-plugin": "^4.26.0",
diff --git a/FrontEnd/src/app/utilities/useReverseScrollPositionRemember.ts b/FrontEnd/src/app/utilities/useReverseScrollPositionRemember.ts
index c86ffa74..a97d7660 100644
--- a/FrontEnd/src/app/utilities/useReverseScrollPositionRemember.ts
+++ b/FrontEnd/src/app/utilities/useReverseScrollPositionRemember.ts
@@ -2,6 +2,10 @@ import React from "react";
let on = false;
+let reverseScrollPosition = getReverseScrollPosition();
+let reverseScrollToPosition: number | null = null;
+let lastScrollPosition = window.scrollY;
+
export function getReverseScrollPosition(): number {
if (document.documentElement.scrollHeight <= window.innerHeight) {
return 0;
@@ -20,22 +24,47 @@ export function scrollToReverseScrollPosition(reversePosition: number): void {
const old = document.documentElement.style.scrollBehavior;
document.documentElement.style.scrollBehavior = "auto";
- window.scrollTo(
- 0,
- document.documentElement.scrollHeight - window.innerHeight - reversePosition
- );
+ const newPosition =
+ document.documentElement.scrollHeight -
+ window.innerHeight -
+ reversePosition;
+
+ reverseScrollToPosition = newPosition;
+
+ window.scrollTo(0, newPosition);
document.documentElement.style.scrollBehavior = old;
}
-let scrollPosition = getReverseScrollPosition();
-
const scrollListener = (): void => {
- scrollPosition = getReverseScrollPosition();
+ if (
+ reverseScrollToPosition != null &&
+ Math.abs(window.scrollY - reverseScrollToPosition) > 50
+ ) {
+ console.log(
+ `Reverse scroll position coerce. Required: ${reverseScrollToPosition}. Actual: ${window.scrollY}.`
+ );
+ scrollToReverseScrollPosition(reverseScrollPosition);
+ return;
+ }
+ if (
+ reverseScrollToPosition == null &&
+ Math.abs(window.scrollY - lastScrollPosition) > 1000
+ ) {
+ console.log(
+ `Scroll jump detected. New: ${window.scrollY}. Old: ${lastScrollPosition}.`
+ );
+ scrollToReverseScrollPosition(reverseScrollPosition);
+ return;
+ }
+
+ reverseScrollToPosition = null;
+ lastScrollPosition = window.scrollY;
+ reverseScrollPosition = getReverseScrollPosition();
};
const resizeObserver = new ResizeObserver(() => {
- scrollToReverseScrollPosition(scrollPosition);
+ scrollToReverseScrollPosition(reverseScrollPosition);
});
export default function useReverseScrollPositionRemember(): void {
diff --git a/FrontEnd/src/app/utilities/useScrollToTop.ts b/FrontEnd/src/app/utilities/useScrollToTop.ts
new file mode 100644
index 00000000..da63cb0a
--- /dev/null
+++ b/FrontEnd/src/app/utilities/useScrollToTop.ts
@@ -0,0 +1,49 @@
+import React from "react";
+import { fromEvent } from "rxjs";
+import { filter, throttleTime, tap } from "rxjs/operators";
+
+function useScrollToTop(
+ handler: () => void,
+ enable = true,
+ option = {
+ maxOffset: 50,
+ throttle: 1000,
+ }
+): void {
+ const handlerRef = React.useRef<(() => void) | null>(null);
+
+ React.useEffect(() => {
+ handlerRef.current = handler;
+
+ return () => {
+ handlerRef.current = null;
+ };
+ }, [handler]);
+
+ React.useEffect(() => {
+ const subscription = fromEvent(window, "scroll")
+ .pipe(
+ tap(() => {
+ console.log(
+ `Scroll event fired: ${window.scrollY}, time: ${Date.now()}.`
+ );
+ }),
+ filter(() => {
+ return window.scrollY <= option.maxOffset;
+ }),
+ throttleTime(option.throttle)
+ )
+ .subscribe(() => {
+ if (enable) {
+ console.log(`Fire scroll to top event, time: ${Date.now()}.`);
+ handlerRef.current?.();
+ }
+ });
+
+ return () => {
+ subscription.unsubscribe();
+ };
+ }, [enable, option.maxOffset, option.throttle]);
+}
+
+export default useScrollToTop;
diff --git a/FrontEnd/src/app/views/timeline-common/TimelinePagedPostListView.tsx b/FrontEnd/src/app/views/timeline-common/TimelinePagedPostListView.tsx
index 69fd9207..37f02a82 100644
--- a/FrontEnd/src/app/views/timeline-common/TimelinePagedPostListView.tsx
+++ b/FrontEnd/src/app/views/timeline-common/TimelinePagedPostListView.tsx
@@ -1,8 +1,9 @@
import React from "react";
-import { fromEvent } from "rxjs";
import { HttpTimelinePostInfo } from "@/http/timeline";
+import useScrollToTop from "@/utilities/useScrollToTop";
+
import TimelinePostListView from "./TimelinePostListView";
export interface TimelinePagedPostListViewProps {
@@ -25,16 +26,9 @@ const TimelinePagedPostListView: React.FC<TimelinePagedPostListViewProps> = (
: posts.slice(-lastViewCount);
}, [posts, lastViewCount]);
- React.useEffect(() => {
- if (lastViewCount < posts.length) {
- const subscription = fromEvent(window, "scroll").subscribe(() => {
- if (window.scrollY === 0) {
- setLastViewCount(lastViewCount + 10);
- }
- });
- return () => subscription.unsubscribe();
- }
- }, [lastViewCount, posts]);
+ useScrollToTop(() => {
+ setLastViewCount(lastViewCount + 10);
+ }, lastViewCount < posts.length);
return (
<TimelinePostListView
diff --git a/FrontEnd/src/app/views/timeline-common/TimelinePostView.tsx b/FrontEnd/src/app/views/timeline-common/TimelinePostView.tsx
index c9fec919..2f778ab1 100644
--- a/FrontEnd/src/app/views/timeline-common/TimelinePostView.tsx
+++ b/FrontEnd/src/app/views/timeline-common/TimelinePostView.tsx
@@ -28,15 +28,18 @@ const TimelinePostView: React.FC<TimelinePostViewProps> = (props) => {
React.useState<boolean>(false);
const [deleteDialog, setDeleteDialog] = React.useState<boolean>(false);
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
- const cardRef = React.useRef<HTMLDivElement>(null!);
+ const cardRef = React.useRef<HTMLDivElement>(null);
React.useEffect(() => {
const cardIntersectionObserver = new IntersectionObserver(([e]) => {
if (e.intersectionRatio > 0) {
- cardRef.current.style.animationName = "timeline-post-enter";
+ if (cardRef.current != null) {
+ cardRef.current.style.animationName = "timeline-post-enter";
+ }
}
});
- cardIntersectionObserver.observe(cardRef.current);
+ if (cardRef.current) {
+ cardIntersectionObserver.observe(cardRef.current);
+ }
return () => {
cardIntersectionObserver.disconnect();