mirror of
https://github.com/LemmyNet/lemmy-ui.git
synced 2024-11-22 12:21:13 +00:00
Merge branch 'main' into captcha-csp
This commit is contained in:
commit
31183f2542
15 changed files with 193 additions and 175 deletions
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "lemmy-ui",
|
||||
"version": "0.18.1-rc.2",
|
||||
"version": "0.18.1-rc.3",
|
||||
"description": "An isomorphic UI for lemmy",
|
||||
"repository": "https://github.com/LemmyNet/lemmy-ui",
|
||||
"license": "AGPL-3.0",
|
||||
|
@ -52,6 +52,7 @@
|
|||
"cross-fetch": "^3.1.5",
|
||||
"css-loader": "^6.7.3",
|
||||
"date-fns": "^2.30.0",
|
||||
"date-fns-tz": "^2.0.0",
|
||||
"emoji-mart": "^5.4.0",
|
||||
"emoji-short-name": "^2.0.0",
|
||||
"express": "~4.18.2",
|
||||
|
|
|
@ -198,9 +198,9 @@ blockquote {
|
|||
|
||||
.thumbnail {
|
||||
object-fit: cover;
|
||||
aspect-ratio: 4/3;
|
||||
width: 100%;
|
||||
max-height: 6rem;
|
||||
aspect-ratio: 1/1;
|
||||
width: 5rem;
|
||||
height: 5rem;
|
||||
}
|
||||
|
||||
.thumbnail svg {
|
||||
|
|
17
src/server/handlers/security-handler.ts
Normal file
17
src/server/handlers/security-handler.ts
Normal file
|
@ -0,0 +1,17 @@
|
|||
import type { Response } from "express";
|
||||
|
||||
export default async ({ res }: { res: Response }) => {
|
||||
res.setHeader("content-type", "text/plain; charset=utf-8");
|
||||
|
||||
res.send(
|
||||
`Contact: mailto:security@lemmy.ml
|
||||
Contact: mailto:admin@` +
|
||||
process.env.LEMMY_UI_LEMMY_EXTERNAL_HOST +
|
||||
`
|
||||
Contact: mailto:security@` +
|
||||
process.env.LEMMY_UI_LEMMY_EXTERNAL_HOST +
|
||||
`
|
||||
Expires: 2024-01-01T04:59:00.000Z
|
||||
`
|
||||
);
|
||||
};
|
|
@ -5,6 +5,7 @@ import process from "process";
|
|||
import CatchAllHandler from "./handlers/catch-all-handler";
|
||||
import ManifestHandler from "./handlers/manifest-handler";
|
||||
import RobotsHandler from "./handlers/robots-handler";
|
||||
import SecurityHandler from "./handlers/security-handler";
|
||||
import ServiceWorkerHandler from "./handlers/service-worker-handler";
|
||||
import ThemeHandler from "./handlers/theme-handler";
|
||||
import ThemesListHandler from "./handlers/themes-list-handler";
|
||||
|
@ -25,6 +26,7 @@ if (!process.env["LEMMY_UI_DISABLE_CSP"] && !process.env["LEMMY_UI_DEBUG"]) {
|
|||
server.use(setDefaultCsp);
|
||||
}
|
||||
|
||||
server.get("/.well-known/security.txt", SecurityHandler);
|
||||
server.get("/robots.txt", RobotsHandler);
|
||||
server.get("/service-worker.js", ServiceWorkerHandler);
|
||||
server.get("/manifest.webmanifest", ManifestHandler);
|
||||
|
|
|
@ -84,6 +84,8 @@ export class ImageUploadForm extends Component<
|
|||
if (res.state === "success") {
|
||||
if (res.data.msg === "ok") {
|
||||
i.props.onUpload(res.data.url as string);
|
||||
} else if (res.data.msg === "too_large") {
|
||||
toast(I18NextService.i18n.t("upload_too_large"), "danger");
|
||||
} else {
|
||||
toast(JSON.stringify(res), "danger");
|
||||
}
|
||||
|
|
|
@ -443,6 +443,10 @@ export class MarkdownTextArea extends Component<
|
|||
const textarea: any = document.getElementById(i.id);
|
||||
autosize.update(textarea);
|
||||
pictrsDeleteToast(image.name, res.data.delete_url as string);
|
||||
} else if (res.data.msg === "too_large") {
|
||||
toast(I18NextService.i18n.t("upload_too_large"), "danger");
|
||||
i.setState({ imageUploadStatus: undefined });
|
||||
throw JSON.stringify(res.data);
|
||||
} else {
|
||||
throw JSON.stringify(res.data);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { capitalizeFirstLetter, formatPastDate } from "@utils/helpers";
|
||||
import format from "date-fns/format";
|
||||
import { formatInTimeZone } from "date-fns-tz";
|
||||
import parseISO from "date-fns/parseISO";
|
||||
import { Component } from "inferno";
|
||||
import { I18NextService } from "../../services";
|
||||
|
@ -13,7 +13,9 @@ interface MomentTimeProps {
|
|||
}
|
||||
|
||||
function formatDate(input: string) {
|
||||
return format(parseISO(input), "PPPPpppp");
|
||||
const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
||||
const parsed = parseISO(input + "Z");
|
||||
return formatInTimeZone(parsed, tz, "PPPPpppp");
|
||||
}
|
||||
|
||||
export class MomentTime extends Component<MomentTimeProps, any> {
|
||||
|
|
|
@ -508,6 +508,8 @@ export class EmojiForm extends Component<EmojiFormProps, EmojiFormState> {
|
|||
{ form: form, index: index, overrideValue: res.data.url as string },
|
||||
event
|
||||
);
|
||||
} else if (res.data.msg === "too_large") {
|
||||
toast(I18NextService.i18n.t("upload_too_large"), "danger");
|
||||
} else {
|
||||
toast(JSON.stringify(res), "danger");
|
||||
}
|
||||
|
|
|
@ -187,6 +187,8 @@ function handleImageUpload(i: PostForm, event: any) {
|
|||
imageLoading: false,
|
||||
imageDeleteUrl: res.data.delete_url as string,
|
||||
});
|
||||
} else if (res.data.msg === "too_large") {
|
||||
toast(I18NextService.i18n.t("upload_too_large"), "danger");
|
||||
} else {
|
||||
toast(JSON.stringify(res), "danger");
|
||||
}
|
||||
|
|
|
@ -333,7 +333,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
|
|||
return (
|
||||
<button
|
||||
type="button"
|
||||
className="d-inline-block position-relative mb-2 p-0 border-0"
|
||||
className="thumbnail rounded overflow-hidden d-inline-block position-relative mb-2 p-0 border-0"
|
||||
data-tippy-content={I18NextService.i18n.t("expand_here")}
|
||||
onClick={linkEvent(this, this.handleImageExpandClick)}
|
||||
aria-label={I18NextService.i18n.t("expand_here")}
|
||||
|
@ -348,7 +348,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
|
|||
} else if (!this.props.hideImage && url && thumbnail && this.imageSrc) {
|
||||
return (
|
||||
<a
|
||||
className="text-body d-inline-block position-relative mb-2"
|
||||
className="thumbnail rounded bg-light d-flex justify-content-center"
|
||||
href={url}
|
||||
rel={relTags}
|
||||
title={url}
|
||||
|
@ -702,6 +702,50 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
|
|||
{(this.canMod_ || this.canAdmin_) && (
|
||||
<li>{this.modRemoveButton}</li>
|
||||
)}
|
||||
|
||||
{this.canMod_ && (
|
||||
<>
|
||||
<li>
|
||||
<hr className="dropdown-divider" />
|
||||
</li>
|
||||
{!this.creatorIsMod_ &&
|
||||
(!post_view.creator_banned_from_community ? (
|
||||
<li>{this.modBanFromCommunityButton}</li>
|
||||
) : (
|
||||
<li>{this.modUnbanFromCommunityButton}</li>
|
||||
))}
|
||||
{!post_view.creator_banned_from_community && (
|
||||
<li>{this.addModToCommunityButton}</li>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{(amCommunityCreator(post_view.creator.id, this.props.moderators) ||
|
||||
this.canAdmin_) &&
|
||||
this.creatorIsMod_ && <li>{this.transferCommunityButton}</li>}
|
||||
|
||||
{/* Admins can ban from all, and appoint other admins */}
|
||||
{this.canAdmin_ && (
|
||||
<>
|
||||
<li>
|
||||
<hr className="dropdown-divider" />
|
||||
</li>
|
||||
{!this.creatorIsAdmin_ && (
|
||||
<>
|
||||
{!isBanned(post_view.creator) ? (
|
||||
<li>{this.modBanButton}</li>
|
||||
) : (
|
||||
<li>{this.modUnbanButton}</li>
|
||||
)}
|
||||
<li>{this.purgePersonButton}</li>
|
||||
<li>{this.purgePostButton}</li>
|
||||
</>
|
||||
)}
|
||||
{!isBanned(post_view.creator) && post_view.creator.local && (
|
||||
<li>{this.toggleAdminButton}</li>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</ul>
|
||||
</div>
|
||||
</>
|
||||
|
@ -969,9 +1013,8 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
|
|||
get modBanFromCommunityButton() {
|
||||
return (
|
||||
<button
|
||||
className="btn btn-link btn-animate text-muted py-0"
|
||||
className="btn btn-link btn-sm d-flex align-items-center rounded-0 dropdown-item"
|
||||
onClick={linkEvent(this, this.handleModBanFromCommunityShow)}
|
||||
aria-label={I18NextService.i18n.t("ban_from_community")}
|
||||
>
|
||||
{I18NextService.i18n.t("ban_from_community")}
|
||||
</button>
|
||||
|
@ -981,9 +1024,8 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
|
|||
get modUnbanFromCommunityButton() {
|
||||
return (
|
||||
<button
|
||||
className="btn btn-link btn-animate text-muted py-0"
|
||||
className="btn btn-link btn-sm d-flex align-items-center rounded-0 dropdown-item"
|
||||
onClick={linkEvent(this, this.handleModBanFromCommunitySubmit)}
|
||||
aria-label={I18NextService.i18n.t("unban")}
|
||||
>
|
||||
{this.state.banLoading ? <Spinner /> : I18NextService.i18n.t("unban")}
|
||||
</button>
|
||||
|
@ -993,20 +1035,15 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
|
|||
get addModToCommunityButton() {
|
||||
return (
|
||||
<button
|
||||
className="btn btn-link btn-animate text-muted py-0"
|
||||
className="btn btn-link btn-sm d-flex align-items-center rounded-0 dropdown-item"
|
||||
onClick={linkEvent(this, this.handleAddModToCommunity)}
|
||||
aria-label={
|
||||
this.creatorIsMod_
|
||||
? I18NextService.i18n.t("remove_as_mod")
|
||||
: I18NextService.i18n.t("appoint_as_mod")
|
||||
}
|
||||
>
|
||||
{this.state.addModLoading ? (
|
||||
<Spinner />
|
||||
) : this.creatorIsMod_ ? (
|
||||
I18NextService.i18n.t("remove_as_mod")
|
||||
capitalizeFirstLetter(I18NextService.i18n.t("remove_as_mod"))
|
||||
) : (
|
||||
I18NextService.i18n.t("appoint_as_mod")
|
||||
capitalizeFirstLetter(I18NextService.i18n.t("appoint_as_mod"))
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
|
@ -1015,11 +1052,10 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
|
|||
get modBanButton() {
|
||||
return (
|
||||
<button
|
||||
className="btn btn-link btn-animate text-muted py-0"
|
||||
className="btn btn-link btn-sm d-flex align-items-center rounded-0 dropdown-item"
|
||||
onClick={linkEvent(this, this.handleModBanShow)}
|
||||
aria-label={I18NextService.i18n.t("ban_from_site")}
|
||||
>
|
||||
{I18NextService.i18n.t("ban_from_site")}
|
||||
{capitalizeFirstLetter(I18NextService.i18n.t("ban_from_site"))}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
@ -1027,14 +1063,13 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
|
|||
get modUnbanButton() {
|
||||
return (
|
||||
<button
|
||||
className="btn btn-link btn-animate text-muted py-0"
|
||||
className="btn btn-link btn-sm d-flex align-items-center rounded-0 dropdown-item"
|
||||
onClick={linkEvent(this, this.handleModBanSubmit)}
|
||||
aria-label={I18NextService.i18n.t("unban_from_site")}
|
||||
>
|
||||
{this.state.banLoading ? (
|
||||
<Spinner />
|
||||
) : (
|
||||
I18NextService.i18n.t("unban_from_site")
|
||||
capitalizeFirstLetter(I18NextService.i18n.t("unban_from_site"))
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
|
@ -1043,11 +1078,10 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
|
|||
get purgePersonButton() {
|
||||
return (
|
||||
<button
|
||||
className="btn btn-link btn-animate text-muted py-0"
|
||||
className="btn btn-link btn-sm d-flex align-items-center rounded-0 dropdown-item"
|
||||
onClick={linkEvent(this, this.handlePurgePersonShow)}
|
||||
aria-label={I18NextService.i18n.t("purge_user")}
|
||||
>
|
||||
{I18NextService.i18n.t("purge_user")}
|
||||
{capitalizeFirstLetter(I18NextService.i18n.t("purge_user"))}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
@ -1055,11 +1089,10 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
|
|||
get purgePostButton() {
|
||||
return (
|
||||
<button
|
||||
className="btn btn-link btn-animate text-muted py-0"
|
||||
className="btn btn-link btn-sm d-flex align-items-center rounded-0 dropdown-item"
|
||||
onClick={linkEvent(this, this.handlePurgePostShow)}
|
||||
aria-label={I18NextService.i18n.t("purge_post")}
|
||||
>
|
||||
{I18NextService.i18n.t("purge_post")}
|
||||
{capitalizeFirstLetter(I18NextService.i18n.t("purge_post"))}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
@ -1067,20 +1100,31 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
|
|||
get toggleAdminButton() {
|
||||
return (
|
||||
<button
|
||||
className="btn btn-link btn-animate text-muted py-0"
|
||||
className="btn btn-link btn-sm d-flex align-items-center rounded-0 dropdown-item"
|
||||
onClick={linkEvent(this, this.handleAddAdmin)}
|
||||
>
|
||||
{this.state.addAdminLoading ? (
|
||||
<Spinner />
|
||||
) : this.creatorIsAdmin_ ? (
|
||||
I18NextService.i18n.t("remove_as_admin")
|
||||
capitalizeFirstLetter(I18NextService.i18n.t("remove_as_admin"))
|
||||
) : (
|
||||
I18NextService.i18n.t("appoint_as_admin")
|
||||
capitalizeFirstLetter(I18NextService.i18n.t("appoint_as_admin"))
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
get transferCommunityButton() {
|
||||
return (
|
||||
<button
|
||||
className="btn btn-link btn-sm d-flex align-items-center rounded-0 dropdown-item"
|
||||
onClick={linkEvent(this, this.handleShowConfirmTransferCommunity)}
|
||||
>
|
||||
{capitalizeFirstLetter(I18NextService.i18n.t("transfer_community"))}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
get modRemoveButton() {
|
||||
const removed = this.postView.post.removed;
|
||||
return (
|
||||
|
@ -1095,99 +1139,14 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
|
|||
{this.state.removeLoading ? (
|
||||
<Spinner />
|
||||
) : !removed ? (
|
||||
I18NextService.i18n.t("remove")
|
||||
) : (
|
||||
I18NextService.i18n.t("restore")
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mod/Admin actions to be taken against the author.
|
||||
*/
|
||||
userActionsLine() {
|
||||
// TODO: make nicer
|
||||
const post_view = this.postView;
|
||||
return (
|
||||
this.state.showAdvanced && (
|
||||
<div className="mt-3">
|
||||
{this.canMod_ && (
|
||||
<>
|
||||
{!this.creatorIsMod_ &&
|
||||
(!post_view.creator_banned_from_community
|
||||
? this.modBanFromCommunityButton
|
||||
: this.modUnbanFromCommunityButton)}
|
||||
{!post_view.creator_banned_from_community &&
|
||||
this.addModToCommunityButton}
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Community creators and admins can transfer community to another mod */}
|
||||
{(amCommunityCreator(post_view.creator.id, this.props.moderators) ||
|
||||
this.canAdmin_) &&
|
||||
this.creatorIsMod_ &&
|
||||
(!this.state.showConfirmTransferCommunity ? (
|
||||
<button
|
||||
className="btn btn-link btn-animate text-muted py-0"
|
||||
onClick={linkEvent(
|
||||
this,
|
||||
this.handleShowConfirmTransferCommunity
|
||||
)}
|
||||
aria-label={I18NextService.i18n.t("transfer_community")}
|
||||
>
|
||||
{I18NextService.i18n.t("transfer_community")}
|
||||
</button>
|
||||
capitalizeFirstLetter(I18NextService.i18n.t("remove_post"))
|
||||
) : (
|
||||
<>
|
||||
<button
|
||||
className="d-inline-block me-1 btn btn-link btn-animate text-muted py-0"
|
||||
aria-label={I18NextService.i18n.t("are_you_sure")}
|
||||
>
|
||||
{I18NextService.i18n.t("are_you_sure")}
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-link btn-animate text-muted py-0 d-inline-block me-1"
|
||||
aria-label={I18NextService.i18n.t("yes")}
|
||||
onClick={linkEvent(this, this.handleTransferCommunity)}
|
||||
>
|
||||
{this.state.transferLoading ? (
|
||||
<Spinner />
|
||||
) : (
|
||||
I18NextService.i18n.t("yes")
|
||||
)}
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-link btn-animate text-muted py-0 d-inline-block"
|
||||
onClick={linkEvent(
|
||||
this,
|
||||
this.handleCancelShowConfirmTransferCommunity
|
||||
)}
|
||||
aria-label={I18NextService.i18n.t("no")}
|
||||
>
|
||||
{I18NextService.i18n.t("no")}
|
||||
</button>
|
||||
</>
|
||||
))}
|
||||
{/* Admins can ban from all, and appoint other admins */}
|
||||
{this.canAdmin_ && (
|
||||
<>
|
||||
{!this.creatorIsAdmin_ && (
|
||||
<>
|
||||
{!isBanned(post_view.creator)
|
||||
? this.modBanButton
|
||||
: this.modUnbanButton}
|
||||
{this.purgePersonButton}
|
||||
{this.purgePostButton}
|
||||
{capitalizeFirstLetter(I18NextService.i18n.t("restore"))}{" "}
|
||||
{I18NextService.i18n.t("post")}
|
||||
</>
|
||||
)}
|
||||
{!isBanned(post_view.creator) &&
|
||||
post_view.creator.local &&
|
||||
this.toggleAdminButton}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1218,11 +1177,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
|
|||
value={this.state.removeReason}
|
||||
onInput={linkEvent(this, this.handleModRemoveReasonChange)}
|
||||
/>
|
||||
<button
|
||||
type="submit"
|
||||
className="btn btn-secondary"
|
||||
aria-label={I18NextService.i18n.t("remove_post")}
|
||||
>
|
||||
<button type="submit" className="btn btn-secondary">
|
||||
{this.state.removeLoading ? (
|
||||
<Spinner />
|
||||
) : (
|
||||
|
@ -1231,6 +1186,33 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
|
|||
</button>
|
||||
</form>
|
||||
)}
|
||||
{this.state.showConfirmTransferCommunity && (
|
||||
<>
|
||||
<button className="d-inline-block me-1 btn btn-link btn-animate text-muted py-0">
|
||||
{I18NextService.i18n.t("are_you_sure")}
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-link btn-animate text-muted py-0 d-inline-block me-1"
|
||||
onClick={linkEvent(this, this.handleTransferCommunity)}
|
||||
>
|
||||
{this.state.transferLoading ? (
|
||||
<Spinner />
|
||||
) : (
|
||||
I18NextService.i18n.t("yes")
|
||||
)}
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-link btn-animate text-muted py-0 d-inline-block"
|
||||
onClick={linkEvent(
|
||||
this,
|
||||
this.handleCancelShowConfirmTransferCommunity
|
||||
)}
|
||||
aria-label={I18NextService.i18n.t("no")}
|
||||
>
|
||||
{I18NextService.i18n.t("no")}
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
{this.state.showBanDialog && (
|
||||
<form onSubmit={linkEvent(this, this.handleModBanBothSubmit)}>
|
||||
<div className="mb-3 row col-12">
|
||||
|
@ -1284,11 +1266,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
|
|||
{/* <input type="date" class="form-control me-2" placeholder={I18NextService.i18n.t('expires')} value={this.state.banExpires} onInput={linkEvent(this, this.handleModBanExpiresChange)} /> */}
|
||||
{/* </div> */}
|
||||
<div className="mb-3 row">
|
||||
<button
|
||||
type="submit"
|
||||
className="btn btn-secondary"
|
||||
aria-label={I18NextService.i18n.t("ban")}
|
||||
>
|
||||
<button type="submit" className="btn btn-secondary">
|
||||
{this.state.banLoading ? (
|
||||
<Spinner />
|
||||
) : (
|
||||
|
@ -1317,11 +1295,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
|
|||
value={this.state.reportReason}
|
||||
onInput={linkEvent(this, this.handleReportReasonChange)}
|
||||
/>
|
||||
<button
|
||||
type="submit"
|
||||
className="btn btn-secondary"
|
||||
aria-label={I18NextService.i18n.t("create_report")}
|
||||
>
|
||||
<button type="submit" className="btn btn-secondary">
|
||||
{this.state.reportLoading ? (
|
||||
<Spinner />
|
||||
) : (
|
||||
|
@ -1350,11 +1324,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
|
|||
{this.state.purgeLoading ? (
|
||||
<Spinner />
|
||||
) : (
|
||||
<button
|
||||
type="submit"
|
||||
className="btn btn-secondary"
|
||||
aria-label={purgeTypeText}
|
||||
>
|
||||
<button type="submit" className="btn btn-secondary">
|
||||
{this.state.purgeLoading ? <Spinner /> : { purgeTypeText }}
|
||||
</button>
|
||||
)}
|
||||
|
@ -1409,7 +1379,6 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
|
|||
{this.mobileThumbnail()}
|
||||
|
||||
{this.commentsLine(true)}
|
||||
{this.userActionsLine()}
|
||||
{this.duplicatesLine()}
|
||||
{this.removeAndBanDialogs()}
|
||||
</div>
|
||||
|
@ -1433,15 +1402,14 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
|
|||
)}
|
||||
<div className="col flex-grow-1">
|
||||
<div className="row">
|
||||
<div className="col-sm-3 col-lg-2 pe-0 post-media">
|
||||
<div className="col flex-grow-0 px-0">
|
||||
<div className="">{this.thumbnail()}</div>
|
||||
</div>
|
||||
<div className="col-12 col-sm-9 col-lg-10">
|
||||
<div className="col flex-grow-1">
|
||||
{this.postTitleLine()}
|
||||
{this.createdLine()}
|
||||
{this.commentsLine()}
|
||||
{this.duplicatesLine()}
|
||||
{this.userActionsLine()}
|
||||
{this.removeAndBanDialogs()}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -332,9 +332,7 @@ export class Search extends Component<any, SearchState> {
|
|||
}
|
||||
|
||||
async componentDidMount() {
|
||||
if (
|
||||
!(this.state.isIsomorphic || this.props.history.location.state?.searched)
|
||||
) {
|
||||
if (!this.state.isIsomorphic) {
|
||||
const promises = [this.fetchCommunities()];
|
||||
if (this.state.searchText) {
|
||||
promises.push(this.search());
|
||||
|
@ -432,7 +430,15 @@ export class Search extends Component<any, SearchState> {
|
|||
q: query,
|
||||
auth,
|
||||
};
|
||||
resolveObjectResponse = await client.resolveObject(resolveObjectForm);
|
||||
resolveObjectResponse = await HttpService.silent_client.resolveObject(
|
||||
resolveObjectForm
|
||||
);
|
||||
|
||||
// If we return this object with a state of failed, the catch-all-handler will redirect
|
||||
// to an error page, so we ignore it by covering up the error with the empty state.
|
||||
if (resolveObjectResponse.state === "failed") {
|
||||
resolveObjectResponse = { state: "empty" };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -950,7 +956,7 @@ export class Search extends Component<any, SearchState> {
|
|||
if (auth) {
|
||||
this.setState({ resolveObjectRes: { state: "loading" } });
|
||||
this.setState({
|
||||
resolveObjectRes: await HttpService.client.resolveObject({
|
||||
resolveObjectRes: await HttpService.silent_client.resolveObject({
|
||||
q,
|
||||
auth,
|
||||
}),
|
||||
|
@ -1097,10 +1103,6 @@ export class Search extends Component<any, SearchState> {
|
|||
sort: sort ?? urlSort,
|
||||
};
|
||||
|
||||
this.props.history.push(`/search${getQueryString(queryParams)}`, {
|
||||
searched: true,
|
||||
});
|
||||
|
||||
await this.search();
|
||||
this.props.history.push(`/search${getQueryString(queryParams)}`);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import { getHttpBase } from "@utils/env";
|
||||
import { LemmyHttp } from "lemmy-js-client";
|
||||
import { toast } from "../../shared/toast";
|
||||
import { toast } from "../toast";
|
||||
import { I18NextService } from "./I18NextService";
|
||||
|
||||
type EmptyRequestState = {
|
||||
export type EmptyRequestState = {
|
||||
state: "empty";
|
||||
};
|
||||
|
||||
|
@ -45,7 +45,7 @@ export type WrappedLemmyHttp = {
|
|||
class WrappedLemmyHttpClient {
|
||||
#client: LemmyHttp;
|
||||
|
||||
constructor(client: LemmyHttp) {
|
||||
constructor(client: LemmyHttp, silent = false) {
|
||||
this.#client = client;
|
||||
|
||||
for (const key of Object.getOwnPropertyNames(
|
||||
|
@ -61,8 +61,10 @@ class WrappedLemmyHttpClient {
|
|||
state: !(res === undefined || res === null) ? "success" : "empty",
|
||||
};
|
||||
} catch (error) {
|
||||
if (!silent) {
|
||||
console.error(`API error: ${error}`);
|
||||
toast(I18NextService.i18n.t(error), "danger");
|
||||
}
|
||||
return {
|
||||
state: "failed",
|
||||
msg: error,
|
||||
|
@ -74,16 +76,23 @@ class WrappedLemmyHttpClient {
|
|||
}
|
||||
}
|
||||
|
||||
export function wrapClient(client: LemmyHttp) {
|
||||
return new WrappedLemmyHttpClient(client) as unknown as WrappedLemmyHttp; // unfortunately, this verbose cast is necessary
|
||||
export function wrapClient(client: LemmyHttp, silent = false) {
|
||||
// unfortunately, this verbose cast is necessary
|
||||
return new WrappedLemmyHttpClient(
|
||||
client,
|
||||
silent
|
||||
) as unknown as WrappedLemmyHttp;
|
||||
}
|
||||
|
||||
export class HttpService {
|
||||
static #_instance: HttpService;
|
||||
#silent_client: WrappedLemmyHttp;
|
||||
#client: WrappedLemmyHttp;
|
||||
|
||||
private constructor() {
|
||||
this.#client = wrapClient(new LemmyHttp(getHttpBase()));
|
||||
const lemmyHttp = new LemmyHttp(getHttpBase());
|
||||
this.#client = wrapClient(lemmyHttp);
|
||||
this.#silent_client = wrapClient(lemmyHttp, true);
|
||||
}
|
||||
|
||||
static get #Instance() {
|
||||
|
@ -93,4 +102,8 @@ export class HttpService {
|
|||
public static get client() {
|
||||
return this.#Instance.#client;
|
||||
}
|
||||
|
||||
public static get silent_client() {
|
||||
return this.#Instance.#silent_client;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,11 +2,8 @@ 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(),
|
||||
{
|
||||
const parsed = parseISO((dateString ?? Date.now().toString()) + "Z");
|
||||
return formatDistanceStrict(parsed, new Date(), {
|
||||
addSuffix: true,
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -96,6 +96,7 @@ const createClientConfig = (_env, mode) => {
|
|||
entry: "./src/client/index.tsx",
|
||||
output: {
|
||||
filename: "js/client.js",
|
||||
publicPath: "/static/",
|
||||
},
|
||||
plugins: [
|
||||
...base.plugins,
|
||||
|
@ -106,7 +107,7 @@ const createClientConfig = (_env, mode) => {
|
|||
"/": "/static/",
|
||||
},
|
||||
cacheId: "lemmy",
|
||||
include: [/(assets|styles)\/.+\..+|client\.js$/g],
|
||||
include: [/(assets|styles|js)\/.+\..+$/g],
|
||||
inlineWorkboxRuntime: true,
|
||||
runtimeCaching: [
|
||||
{
|
||||
|
|
|
@ -3237,6 +3237,11 @@ dashdash@^1.12.0:
|
|||
dependencies:
|
||||
assert-plus "^1.0.0"
|
||||
|
||||
date-fns-tz@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/date-fns-tz/-/date-fns-tz-2.0.0.tgz#1b14c386cb8bc16fc56fe333d4fc34ae1d1099d5"
|
||||
integrity sha512-OAtcLdB9vxSXTWHdT8b398ARImVwQMyjfYGkKD2zaGpHseG2UPHbHjXELReErZFxWdSLph3c2zOaaTyHfOhERQ==
|
||||
|
||||
date-fns@^2.30.0:
|
||||
version "2.30.0"
|
||||
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.30.0.tgz#f367e644839ff57894ec6ac480de40cae4b0f4d0"
|
||||
|
|
Loading…
Reference in a new issue