Merge branch 'main' into fix/add-aria-describedby-lang-warn
This commit is contained in:
commit
dac0fa50ad
29 changed files with 24544 additions and 120 deletions
1
src/assets/css/themes/_variables.darkly-compact.scss
Normal file
1
src/assets/css/themes/_variables.darkly-compact.scss
Normal file
|
@ -0,0 +1 @@
|
|||
@import "variables.darkly";
|
1
src/assets/css/themes/_variables.litely-compact.scss
Normal file
1
src/assets/css/themes/_variables.litely-compact.scss
Normal file
|
@ -0,0 +1 @@
|
|||
@import "variables.litely";
|
12041
src/assets/css/themes/darkly-compact.css
Normal file
12041
src/assets/css/themes/darkly-compact.css
Normal file
File diff suppressed because it is too large
Load diff
59
src/assets/css/themes/darkly-compact.scss
Normal file
59
src/assets/css/themes/darkly-compact.scss
Normal file
|
@ -0,0 +1,59 @@
|
|||
@import "variables.darkly-compact";
|
||||
|
||||
/*
|
||||
GENERAL
|
||||
*/
|
||||
|
||||
// Desktop Breakpoint
|
||||
$container-max-widths: (
|
||||
lg: 1920px,
|
||||
);
|
||||
|
||||
// Reduce hr height
|
||||
hr.my-3 {
|
||||
margin-top: 0.5rem !important;
|
||||
margin-bottom: 0.5rem !important;
|
||||
}
|
||||
|
||||
/*
|
||||
POST-LISTING
|
||||
*/
|
||||
|
||||
.post-listing {
|
||||
line-height: 1;
|
||||
|
||||
.post-title h5 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.post-title + p {
|
||||
padding-top: 0.125rem !important;
|
||||
padding-bottom: 0.125rem !important;
|
||||
}
|
||||
|
||||
.community-link {
|
||||
padding-left: 0.125rem;
|
||||
}
|
||||
|
||||
.person-listing {
|
||||
padding-right: 0.125rem;
|
||||
}
|
||||
|
||||
ul.list-inline {
|
||||
&.mt-2 {
|
||||
margin-top: 0.125rem !important;
|
||||
}
|
||||
&.mb-1 {
|
||||
margin-bottom: 0.125rem !important;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-sm {
|
||||
--bs-btn-padding-y: 0;
|
||||
}
|
||||
.img-icon {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@import "../../../../node_modules/bootstrap/scss/bootstrap";
|
12040
src/assets/css/themes/litely-compact.css
Normal file
12040
src/assets/css/themes/litely-compact.css
Normal file
File diff suppressed because it is too large
Load diff
59
src/assets/css/themes/litely-compact.scss
Normal file
59
src/assets/css/themes/litely-compact.scss
Normal file
|
@ -0,0 +1,59 @@
|
|||
@import "variables.litely-compact";
|
||||
|
||||
/*
|
||||
GENERAL
|
||||
*/
|
||||
|
||||
// Desktop Breakpoint
|
||||
$container-max-widths: (
|
||||
lg: 1920px,
|
||||
);
|
||||
|
||||
// Reduce hr height
|
||||
hr.my-3 {
|
||||
margin-top: 0.5rem !important;
|
||||
margin-bottom: 0.5rem !important;
|
||||
}
|
||||
|
||||
/*
|
||||
POST-LISTING
|
||||
*/
|
||||
|
||||
.post-listing {
|
||||
line-height: 1;
|
||||
|
||||
.post-title h5 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.post-title + p {
|
||||
padding-top: 0.125rem !important;
|
||||
padding-bottom: 0.125rem !important;
|
||||
}
|
||||
|
||||
.community-link {
|
||||
padding-left: 0.125rem;
|
||||
}
|
||||
|
||||
.person-listing {
|
||||
padding-right: 0.125rem;
|
||||
}
|
||||
|
||||
ul.list-inline {
|
||||
&.mt-2 {
|
||||
margin-top: 0.125rem !important;
|
||||
}
|
||||
&.mb-1 {
|
||||
margin-bottom: 0.125rem !important;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-sm {
|
||||
--bs-btn-padding-y: 0;
|
||||
}
|
||||
.img-icon {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@import "../../../../node_modules/bootstrap/scss/bootstrap";
|
|
@ -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();
|
||||
|
|
|
@ -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}`);
|
||||
});
|
||||
|
||||
|
|
|
@ -7,8 +7,10 @@ const extraThemesFolder =
|
|||
const themes: ReadonlyArray<string> = [
|
||||
"darkly",
|
||||
"darkly-red",
|
||||
"darkly-compact",
|
||||
"litely",
|
||||
"litely-red",
|
||||
"litely-compact",
|
||||
];
|
||||
|
||||
export async function buildThemeList(): Promise<ReadonlyArray<string>> {
|
||||
|
|
|
@ -4,7 +4,7 @@ import { Provider } from "inferno-i18next-dess";
|
|||
import { Route, Switch } from "inferno-router";
|
||||
import { IsoDataOptionalSite } from "../../interfaces";
|
||||
import { routes } from "../../routes";
|
||||
import { I18NextService } from "../../services";
|
||||
import { FirstLoadService, I18NextService } from "../../services";
|
||||
import AuthGuard from "../common/auth-guard";
|
||||
import ErrorGuard from "../common/error-guard";
|
||||
import { ErrorPage } from "./error-page";
|
||||
|
@ -45,27 +45,35 @@ export class App extends Component<any, any> {
|
|||
<Navbar siteRes={siteRes} />
|
||||
<div className="mt-4 p-0 fl-1">
|
||||
<Switch>
|
||||
{routes.map(({ path, component: RouteComponent }) => (
|
||||
<Route
|
||||
key={path}
|
||||
path={path}
|
||||
exact
|
||||
component={routeProps => (
|
||||
<ErrorGuard>
|
||||
<main tabIndex={-1} ref={this.mainContentRef}>
|
||||
{RouteComponent &&
|
||||
(isAuthPath(path ?? "") ? (
|
||||
<AuthGuard>
|
||||
<RouteComponent {...routeProps} />
|
||||
</AuthGuard>
|
||||
) : (
|
||||
<RouteComponent {...routeProps} />
|
||||
))}
|
||||
</main>
|
||||
</ErrorGuard>
|
||||
)}
|
||||
/>
|
||||
))}
|
||||
{routes.map(
|
||||
({ path, component: RouteComponent, fetchInitialData }) => (
|
||||
<Route
|
||||
key={path}
|
||||
path={path}
|
||||
exact
|
||||
component={routeProps => {
|
||||
if (!fetchInitialData) {
|
||||
FirstLoadService.falsify();
|
||||
}
|
||||
|
||||
return (
|
||||
<ErrorGuard>
|
||||
<main tabIndex={-1} ref={this.mainContentRef}>
|
||||
{RouteComponent &&
|
||||
(isAuthPath(path ?? "") ? (
|
||||
<AuthGuard>
|
||||
<RouteComponent {...routeProps} />
|
||||
</AuthGuard>
|
||||
) : (
|
||||
<RouteComponent {...routeProps} />
|
||||
))}
|
||||
</main>
|
||||
</ErrorGuard>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
<Route component={ErrorPage} />
|
||||
</Switch>
|
||||
</div>
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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";
|
||||
|
@ -101,6 +106,7 @@ interface HomeState {
|
|||
showTrendingMobile: boolean;
|
||||
showSidebarMobile: boolean;
|
||||
subscribedCollapsed: boolean;
|
||||
scrolled: boolean;
|
||||
tagline?: string;
|
||||
siteRes: GetSiteResponse;
|
||||
finished: Map<CommentId, boolean | undefined>;
|
||||
|
@ -217,6 +223,7 @@ export class Home extends Component<any, HomeState> {
|
|||
postsRes: { state: "empty" },
|
||||
commentsRes: { state: "empty" },
|
||||
trendingCommunitiesRes: { state: "empty" },
|
||||
scrolled: true,
|
||||
siteRes: this.isoData.site_res,
|
||||
showSubscribedMobile: false,
|
||||
showTrendingMobile: false,
|
||||
|
@ -276,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 ||
|
||||
|
@ -620,6 +633,11 @@ export class Home extends Component<any, HomeState> {
|
|||
search: getQueryString(queryParams),
|
||||
});
|
||||
|
||||
if (!this.state.scrolled) {
|
||||
this.setState({ scrolled: true });
|
||||
setTimeout(() => window.scrollTo(0, 0), 0);
|
||||
}
|
||||
|
||||
await this.fetchData();
|
||||
}
|
||||
|
||||
|
@ -643,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>
|
||||
|
@ -770,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({
|
||||
|
@ -815,23 +848,23 @@ export class Home extends Component<any, HomeState> {
|
|||
}
|
||||
|
||||
handlePageChange(page: number) {
|
||||
this.setState({ scrolled: false });
|
||||
this.updateUrl({ page });
|
||||
window.scrollTo(0, 0);
|
||||
}
|
||||
|
||||
handleSortChange(val: SortType) {
|
||||
this.setState({ scrolled: false });
|
||||
this.updateUrl({ sort: val, page: 1 });
|
||||
window.scrollTo(0, 0);
|
||||
}
|
||||
|
||||
handleListingTypeChange(val: ListingType) {
|
||||
this.setState({ scrolled: false });
|
||||
this.updateUrl({ listingType: val, page: 1 });
|
||||
window.scrollTo(0, 0);
|
||||
}
|
||||
|
||||
handleDataTypeChange(val: DataType) {
|
||||
this.setState({ scrolled: false });
|
||||
this.updateUrl({ dataType: val, page: 1 });
|
||||
window.scrollTo(0, 0);
|
||||
}
|
||||
|
||||
async handleAddModToCommunity(form: AddModToCommunity) {
|
||||
|
|
|
@ -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>
|
||||
)}
|
||||
</>
|
||||
|
|
|
@ -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 && (
|
||||
|
|
|
@ -1177,7 +1177,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);
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import { isBrowser } from "@utils/browser";
|
||||
|
||||
export class FirstLoadService {
|
||||
#isFirstLoad: boolean;
|
||||
static #instance: FirstLoadService;
|
||||
|
@ -15,11 +17,19 @@ export class FirstLoadService {
|
|||
return isFirst;
|
||||
}
|
||||
|
||||
falsify() {
|
||||
this.#isFirstLoad = false;
|
||||
}
|
||||
|
||||
static get #Instance() {
|
||||
return this.#instance ?? (this.#instance = new this());
|
||||
}
|
||||
|
||||
static get isFirstLoad() {
|
||||
return this.#Instance.isFirstLoad;
|
||||
return !isBrowser() || this.#Instance.isFirstLoad;
|
||||
}
|
||||
|
||||
static falsify() {
|
||||
this.#Instance.falsify();
|
||||
}
|
||||
}
|
||||
|
|
60
src/shared/services/HomeCacheService.ts
Normal file
60
src/shared/services/HomeCacheService.ts
Normal 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();
|
||||
}
|
||||
}
|
|
@ -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";
|
||||
|
|
|
@ -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";
|
||||
|
@ -102,6 +103,7 @@ export {
|
|||
selectableLanguages,
|
||||
setIsoData,
|
||||
setTheme,
|
||||
setupDateFns,
|
||||
showAvatars,
|
||||
showLocal,
|
||||
showScores,
|
||||
|
|
19
src/shared/utils/app/setup-date-fns.ts
Normal file
19
src/shared/utils/app/setup-date-fns.ts
Normal 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,
|
||||
});
|
||||
}
|
12
src/shared/utils/helpers/format-past-date.ts
Normal file
12
src/shared/utils/helpers/format-past-date.ts
Normal 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,
|
||||
}
|
||||
);
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue