Merge branch 'main' into feat/default-to-user-primary-lang

This commit is contained in:
Jay Sitter 2023-06-23 15:37:54 -04:00 committed by GitHub
commit 0c87ee9dab
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
27 changed files with 317 additions and 100 deletions

View file

@ -1,4 +1,4 @@
import { initializeSite } from "@utils/app";
import { initializeSite, setupDateFns } from "@utils/app";
import { hydrate } from "inferno-hydrate";
import { Router } from "inferno-router";
import { App } from "../shared/components/app/app";
@ -7,16 +7,22 @@ import { HistoryService } from "../shared/services";
import "bootstrap/js/dist/collapse";
import "bootstrap/js/dist/dropdown";
initializeSite(window.isoData.site_res);
async function startClient() {
initializeSite(window.isoData.site_res);
const wrapper = (
<Router history={HistoryService.history}>
<App />
</Router>
);
await setupDateFns();
const root = document.getElementById("root");
const wrapper = (
<Router history={HistoryService.history}>
<App />
</Router>
);
if (root) {
hydrate(wrapper, root);
const root = document.getElementById("root");
if (root) {
hydrate(wrapper, root);
}
}
startClient();

View file

@ -1,3 +1,4 @@
import { setupDateFns } from "@utils/app";
import express from "express";
import path from "path";
import process from "process";
@ -31,6 +32,7 @@ server.get("/css/themelist", ThemesListHandler);
server.get("/*", CatchAllHandler);
server.listen(Number(port), hostname, () => {
setupDateFns();
console.log(`http://${hostname}:${port}`);
});

View file

@ -16,6 +16,9 @@ import {
isMod,
} from "@utils/roles";
import classNames from "classnames";
import isBefore from "date-fns/isBefore";
import parseISO from "date-fns/parseISO";
import subMinutes from "date-fns/subMinutes";
import { Component, InfernoNode, linkEvent } from "inferno";
import { Link } from "inferno-router";
import {
@ -46,7 +49,6 @@ import {
SaveComment,
TransferCommunity,
} from "lemmy-js-client";
import moment from "moment";
import { commentTreeMaxDepth } from "../../config";
import {
BanType,
@ -1451,9 +1453,9 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
}
get isCommentNew(): boolean {
const now = moment.utc().subtract(10, "minutes");
const then = moment.utc(this.commentView.comment.published);
return now.isBefore(then);
const now = subMinutes(new Date(), 10);
const then = parseISO(this.commentView.comment.published);
return isBefore(now, then);
}
handleCommentCollapse(i: CommentNode) {

View file

@ -1,6 +1,7 @@
import { capitalizeFirstLetter } from "@utils/helpers";
import { capitalizeFirstLetter, formatPastDate } from "@utils/helpers";
import format from "date-fns/format";
import parseISO from "date-fns/parseISO";
import { Component } from "inferno";
import moment from "moment";
import { I18NextService } from "../../services";
import { Icon } from "./icon";
@ -11,22 +12,24 @@ interface MomentTimeProps {
ignoreUpdated?: boolean;
}
function formatDate(input: string) {
return format(parseISO(input), "PPPPpppp");
}
export class MomentTime extends Component<MomentTimeProps, any> {
constructor(props: any, context: any) {
super(props, context);
moment.locale([...I18NextService.i18n.languages]);
}
createdAndModifiedTimes() {
const updated = this.props.updated;
let line = `${capitalizeFirstLetter(
I18NextService.i18n.t("created")
)}: ${this.format(this.props.published)}`;
)}: ${formatDate(this.props.published)}`;
if (updated) {
line += `\n\n\n${capitalizeFirstLetter(
I18NextService.i18n.t("modified")
)} ${this.format(updated)}`;
)} ${formatDate(updated)}`;
}
return line;
}
@ -39,7 +42,7 @@ export class MomentTime extends Component<MomentTimeProps, any> {
className="moment-time font-italics pointer unselectable"
>
<Icon icon="edit-2" classes="icon-inline me-1" />
{moment.utc(this.props.updated).fromNow(!this.props.showAgo)}
{formatPastDate(this.props.updated)}
</span>
);
} else {
@ -47,15 +50,11 @@ export class MomentTime extends Component<MomentTimeProps, any> {
return (
<span
className="moment-time pointer unselectable"
data-tippy-content={this.format(published)}
data-tippy-content={formatDate(published)}
>
{moment.utc(published).fromNow(!this.props.showAgo)}
{formatPastDate(published)}
</span>
);
}
}
format(input: string): string {
return moment.utc(input).local().format("LLLL");
}
}

View file

@ -4,7 +4,7 @@ import {
CreateCommunity as CreateCommunityI,
GetSiteResponse,
} from "lemmy-js-client";
import { HttpService, I18NextService } from "../../services";
import { FirstLoadService, HttpService, I18NextService } from "../../services";
import { HtmlTags } from "../common/html-tags";
import { CommunityForm } from "./community-form";
@ -22,6 +22,8 @@ export class CreateCommunity extends Component<any, CreateCommunityState> {
constructor(props: any, context: any) {
super(props, context);
this.handleCommunityCreate = this.handleCommunityCreate.bind(this);
FirstLoadService.isFirstLoad;
}
get documentTitle(): string {

View file

@ -78,7 +78,12 @@ import {
InitialFetchRequest,
} from "../../interfaces";
import { mdToHtml } from "../../markdown";
import { FirstLoadService, I18NextService, UserService } from "../../services";
import {
FirstLoadService,
HomeCacheService,
I18NextService,
UserService,
} from "../../services";
import { HttpService, RequestState } from "../../services/HttpService";
import { setupTippy } from "../../tippy";
import { toast } from "../../toast";
@ -278,9 +283,15 @@ export class Home extends Component<any, HomeState> {
?.content,
isIsomorphic: true,
};
HomeCacheService.postsRes = postsRes;
}
}
componentWillUnmount() {
HomeCacheService.activate();
}
async componentDidMount() {
if (
!this.state.isIsomorphic ||
@ -650,6 +661,8 @@ export class Home extends Component<any, HomeState> {
if (dataType === DataType.Post) {
switch (this.state.postsRes?.state) {
case "empty":
return <div style="min-height: 20000px;"></div>;
case "loading":
return (
<h5>
@ -777,17 +790,30 @@ export class Home extends Component<any, HomeState> {
const { dataType, page, listingType, sort } = getHomeQueryParams();
if (dataType === DataType.Post) {
this.setState({ postsRes: { state: "loading" } });
this.setState({
postsRes: await HttpService.client.getPosts({
page,
limit: fetchLimit,
sort,
saved_only: false,
type_: listingType,
auth,
}),
});
if (HomeCacheService.active) {
const { postsRes, scrollY } = HomeCacheService;
HomeCacheService.deactivate();
this.setState({ postsRes });
window.scrollTo({
left: 0,
top: scrollY,
behavior: "instant",
});
} else {
this.setState({ postsRes: { state: "loading" } });
this.setState({
postsRes: await HttpService.client.getPosts({
page,
limit: fetchLimit,
sort,
saved_only: false,
type_: listingType,
auth,
}),
});
HomeCacheService.postsRes = this.state.postsRes;
}
} else {
this.setState({ commentsRes: { state: "loading" } });
this.setState({

View file

@ -2,7 +2,7 @@ import { setIsoData } from "@utils/app";
import { Component } from "inferno";
import { GetSiteResponse } from "lemmy-js-client";
import { mdToHtml } from "../../markdown";
import { I18NextService } from "../../services";
import { FirstLoadService, I18NextService } from "../../services";
import { HtmlTags } from "../common/html-tags";
interface LegalState {
@ -17,6 +17,8 @@ export class Legal extends Component<any, LegalState> {
constructor(props: any, context: any) {
super(props, context);
FirstLoadService.isFirstLoad;
}
get documentTitle(): string {

View file

@ -3,7 +3,7 @@ import { isBrowser } from "@utils/browser";
import { validEmail } from "@utils/helpers";
import { Component, linkEvent } from "inferno";
import { GetSiteResponse, LoginResponse } from "lemmy-js-client";
import { I18NextService, UserService } from "../../services";
import { FirstLoadService, I18NextService, UserService } from "../../services";
import { HttpService, RequestState } from "../../services/HttpService";
import { toast } from "../../toast";
import { HtmlTags } from "../common/html-tags";
@ -32,6 +32,8 @@ export class Login extends Component<any, State> {
constructor(props: any, context: any) {
super(props, context);
FirstLoadService.isFirstLoad;
}
componentDidMount() {

View file

@ -7,7 +7,7 @@ import {
LoginResponse,
Register,
} from "lemmy-js-client";
import { I18NextService, UserService } from "../../services";
import { FirstLoadService, I18NextService, UserService } from "../../services";
import { HttpService, RequestState } from "../../services/HttpService";
import { Spinner } from "../common/icon";
import { SiteForm } from "./site-form";
@ -47,6 +47,8 @@ export class Setup extends Component<any, State> {
super(props, context);
this.handleCreateSite = this.handleCreateSite.bind(this);
FirstLoadService.isFirstLoad;
}
async componentDidMount() {

View file

@ -14,7 +14,7 @@ import {
} from "lemmy-js-client";
import { joinLemmyUrl } from "../../config";
import { mdToHtml } from "../../markdown";
import { I18NextService, UserService } from "../../services";
import { FirstLoadService, I18NextService, UserService } from "../../services";
import { HttpService, RequestState } from "../../services/HttpService";
import { toast } from "../../toast";
import { HtmlTags } from "../common/html-tags";
@ -84,6 +84,8 @@ export class Signup extends Component<any, State> {
super(props, context);
this.handleAnswerChange = this.handleAnswerChange.bind(this);
FirstLoadService.isFirstLoad;
}
async componentDidMount() {

View file

@ -7,6 +7,7 @@ import {
} from "@utils/app";
import {
debounce,
formatPastDate,
getIdFromString,
getPageFromString,
getQueryParams,
@ -44,7 +45,6 @@ import {
ModlogActionType,
Person,
} from "lemmy-js-client";
import moment from "moment";
import { fetchLimit } from "../config";
import { InitialFetchRequest } from "../interfaces";
import { FirstLoadService, I18NextService } from "../services";
@ -371,7 +371,7 @@ function renderModlogType({ type_, view }: ModlogType) {
)}
{expires && (
<span>
<div>expires: {moment.utc(expires).fromNow()}</div>
<div>expires: {formatPastDate(expires)}</div>
</span>
)}
</>
@ -403,7 +403,7 @@ function renderModlogType({ type_, view }: ModlogType) {
)}
{expires && (
<span>
<div>expires: {moment.utc(expires).fromNow()}</div>
<div>expires: {formatPastDate(expires)}</div>
</span>
)}
</>
@ -467,7 +467,7 @@ function renderModlogType({ type_, view }: ModlogType) {
)}
{expires && (
<span>
<div>expires: {moment.utc(expires).fromNow()}</div>
<div>expires: {formatPastDate(expires)}</div>
</span>
)}
</>

View file

@ -2,7 +2,12 @@ import { myAuth, setIsoData } from "@utils/app";
import { capitalizeFirstLetter } from "@utils/helpers";
import { Component, linkEvent } from "inferno";
import { GetSiteResponse, LoginResponse } from "lemmy-js-client";
import { HttpService, I18NextService, UserService } from "../../services";
import {
FirstLoadService,
HttpService,
I18NextService,
UserService,
} from "../../services";
import { RequestState } from "../../services/HttpService";
import { HtmlTags } from "../common/html-tags";
import { Spinner } from "../common/icon";
@ -30,6 +35,8 @@ export class PasswordChange extends Component<any, State> {
constructor(props: any, context: any) {
super(props, context);
FirstLoadService.isFirstLoad;
}
get documentTitle(): string {

View file

@ -23,6 +23,8 @@ import { canMod, isAdmin, isBanned } from "@utils/roles";
import type { QueryParams } from "@utils/types";
import { RouteDataResponse } from "@utils/types";
import classNames from "classnames";
import format from "date-fns/format";
import parseISO from "date-fns/parseISO";
import { NoOptionI18nKeys } from "i18next";
import { Component, linkEvent } from "inferno";
import { Link } from "inferno-router";
@ -70,7 +72,6 @@ import {
SortType,
TransferCommunity,
} from "lemmy-js-client";
import moment from "moment";
import { fetchLimit, relTags } from "../../config";
import { InitialFetchRequest, PersonDetailsView } from "../../interfaces";
import { mdToHtml } from "../../markdown";
@ -613,10 +614,7 @@ export class Profile extends Component<
<Icon icon="cake" />
<span className="ms-2">
{I18NextService.i18n.t("cake_day_title")}{" "}
{moment
.utc(pv.person.published)
.local()
.format("MMM DD, YYYY")}
{format(parseISO(pv.person.published), "PPP")}
</span>
</div>
{!UserService.Instance.myUserInfo && (

View file

@ -29,7 +29,7 @@ import {
SortType,
} from "lemmy-js-client";
import { elementUrl, emDash, relTags } from "../../config";
import { UserService } from "../../services";
import { FirstLoadService, UserService } from "../../services";
import { HttpService, RequestState } from "../../services/HttpService";
import { I18NextService, languages } from "../../services/I18NextService";
import { setupTippy } from "../../tippy";
@ -170,6 +170,8 @@ export class Settings extends Component<any, SettingsState> {
this.handleBlockPerson = this.handleBlockPerson.bind(this);
this.handleBlockCommunity = this.handleBlockCommunity.bind(this);
FirstLoadService.isFirstLoad;
const mui = UserService.Instance.myUserInfo;
if (mui) {
const {
@ -1177,7 +1179,6 @@ export class Settings extends Component<any, SettingsState> {
});
if (saveRes.state === "success") {
UserService.Instance.login(saveRes.data);
location.reload();
toast(I18NextService.i18n.t("saved"));
window.scrollTo(0, 0);
}

View file

@ -1,7 +1,7 @@
import { setIsoData } from "@utils/app";
import { Component } from "inferno";
import { GetSiteResponse, VerifyEmailResponse } from "lemmy-js-client";
import { I18NextService } from "../../services";
import { FirstLoadService, I18NextService } from "../../services";
import { HttpService, RequestState } from "../../services/HttpService";
import { toast } from "../../toast";
import { HtmlTags } from "../common/html-tags";
@ -22,6 +22,8 @@ export class VerifyEmail extends Component<any, State> {
constructor(props: any, context: any) {
super(props, context);
FirstLoadService.isFirstLoad;
}
async verify() {

View file

@ -0,0 +1,60 @@
import { GetPostsResponse } from "lemmy-js-client";
import { RequestState } from "./HttpService.js";
/**
* Service to cache home post listings and restore home state when user uses the browser back buttons.
*/
export class HomeCacheService {
static #_instance: HomeCacheService;
historyIdx = 0;
scrollY = 0;
posts: RequestState<GetPostsResponse> = { state: "empty" };
get active() {
return (
this.historyIdx === window.history.state.idx + 1 &&
this.posts.state === "success"
);
}
deactivate() {
this.historyIdx = 0;
}
activate() {
this.scrollY = window.scrollY;
this.historyIdx = window.history.state.idx;
}
static get #Instance() {
return this.#_instance ?? (this.#_instance = new this());
}
public static get scrollY() {
return this.#Instance.scrollY;
}
public static get historyIdx() {
return this.#Instance.historyIdx;
}
public static set postsRes(posts: RequestState<GetPostsResponse>) {
this.#Instance.posts = posts;
}
public static get postsRes() {
return this.#Instance.posts;
}
public static get active() {
return this.#Instance.active;
}
public static deactivate() {
this.#Instance.deactivate();
}
public static activate() {
this.#Instance.activate();
}
}

View file

@ -1,5 +1,6 @@
export { FirstLoadService } from "./FirstLoadService";
export { HistoryService } from "./HistoryService";
export { HomeCacheService } from "./HomeCacheService";
export { HttpService } from "./HttpService";
export { I18NextService } from "./I18NextService";
export { UserService } from "./UserService";

View file

@ -46,6 +46,7 @@ import searchCommentTree from "./search-comment-tree";
import selectableLanguages from "./selectable-languages";
import setIsoData from "./set-iso-data";
import setTheme from "./set-theme";
import setupDateFns from "./setup-date-fns";
import showAvatars from "./show-avatars";
import showLocal from "./show-local";
import showScores from "./show-scores";
@ -104,6 +105,7 @@ export {
selectableLanguages,
setIsoData,
setTheme,
setupDateFns,
showAvatars,
showLocal,
showScores,

View file

@ -0,0 +1,19 @@
import setDefaultOptions from "date-fns/setDefaultOptions";
import { I18NextService } from "../../services";
export default async function () {
let lang = I18NextService.i18n.language;
if (lang === "en") {
lang = "en-US";
}
const locale = (
await import(
/* webpackExclude: /\.js\.flow$/ */
`date-fns/locale/${lang}`
)
).default;
setDefaultOptions({
locale,
});
}

View file

@ -0,0 +1,12 @@
import formatDistanceStrict from "date-fns/formatDistanceStrict";
import parseISO from "date-fns/parseISO";
export default function (dateString?: string) {
return formatDistanceStrict(
parseISO(dateString ?? Date.now().toString()),
new Date(),
{
addSuffix: true,
}
);
}

View file

@ -1,6 +1,7 @@
import capitalizeFirstLetter from "./capitalize-first-letter";
import debounce from "./debounce";
import editListImmutable from "./edit-list-immutable";
import formatPastDate from "./format-past-date";
import futureDaysToUnixTime from "./future-days-to-unix-time";
import getIdFromString from "./get-id-from-string";
import getPageFromString from "./get-page-from-string";
@ -26,6 +27,7 @@ export {
capitalizeFirstLetter,
debounce,
editListImmutable,
formatPastDate,
futureDaysToUnixTime,
getIdFromString,
getPageFromString,

View file

@ -1,33 +1,13 @@
import moment from "moment";
moment.updateLocale("en", {
relativeTime: {
future: "in %s",
past: "%s ago",
s: "<1m",
ss: "%ds",
m: "1m",
mm: "%dm",
h: "1h",
hh: "%dh",
d: "1d",
dd: "%dd",
w: "1w",
ww: "%dw",
M: "1M",
MM: "%dM",
y: "1Y",
yy: "%dY",
},
});
import getDayOfYear from "date-fns/getDayOfYear";
import getYear from "date-fns/getYear";
import parseISO from "date-fns/parseISO";
export default function isCakeDay(published: string): boolean {
const createDate = moment.utc(published).local();
const currentDate = moment(new Date());
const createDate = parseISO(published);
const currentDate = new Date();
return (
createDate.date() === currentDate.date() &&
createDate.month() === currentDate.month() &&
createDate.year() !== currentDate.year()
getDayOfYear(createDate) === getDayOfYear(currentDate) &&
getYear(createDate) !== getYear(currentDate)
);
}