mirror of
https://github.com/LemmyNet/lemmy-ui.git
synced 2024-11-22 04:11:12 +00:00
Fixing titleOnly, PostSort, and CommentSort. (#2715)
* Fixing titleOnly, PostSort, and CommentSort. * SortType, Tagline, Emojis (#2718) * PostSortType * Hide post sort types in comment view * Tagline * CustomEmoji * Update lemmy-js-client to 0.20.0-alpha.17 * Prompt before leaving unsaved forms * Add cancel buttons, only create taglines when saving * Cleanup SortSelect * Use markdown url for custom emojis This prevent SSR and CSR from rendering different images after changing the image of an emoji, already posted emojis will keep showing the old image. This will also display the same image on different instances that have overlapping custom emojis. * Cleanup EmojisForm sorting * Use existing CommentSortSelect * Simpler sort type conversion --------- Co-authored-by: matc-pub <161147791+matc-pub@users.noreply.github.com>
This commit is contained in:
parent
6057c96f0c
commit
6b5da8cfb1
26 changed files with 815 additions and 580 deletions
|
@ -60,7 +60,7 @@
|
|||
"inferno-router": "^8.2.3",
|
||||
"inferno-server": "^8.2.3",
|
||||
"jwt-decode": "^4.0.0",
|
||||
"lemmy-js-client": "0.20.0-alpha.7",
|
||||
"lemmy-js-client": "0.20.0-alpha.17",
|
||||
"lodash.isequal": "^4.5.0",
|
||||
"markdown-it": "^14.1.0",
|
||||
"markdown-it-bidi": "^0.2.0",
|
||||
|
|
|
@ -114,8 +114,8 @@ importers:
|
|||
specifier: ^4.0.0
|
||||
version: 4.0.0
|
||||
lemmy-js-client:
|
||||
specifier: 0.20.0-alpha.7
|
||||
version: 0.20.0-alpha.7
|
||||
specifier: 0.20.0-alpha.17
|
||||
version: 0.20.0-alpha.17
|
||||
lodash.isequal:
|
||||
specifier: ^4.5.0
|
||||
version: 4.5.0
|
||||
|
@ -3071,8 +3071,8 @@ packages:
|
|||
leac@0.6.0:
|
||||
resolution: {integrity: sha512-y+SqErxb8h7nE/fiEX07jsbuhrpO9lL8eca7/Y1nuWV2moNlXhyd59iDGcRf6moVyDMbmTNzL40SUyrFU/yDpg==}
|
||||
|
||||
lemmy-js-client@0.20.0-alpha.7:
|
||||
resolution: {integrity: sha512-lhPs8gJFLX0EvlwkkgtTF2F/v22lKjcQ81u7u5CShrO05Dii33fZ5x9SrEJYHD0qw4FIN/jpfKucN9QnndScpA==}
|
||||
lemmy-js-client@0.20.0-alpha.17:
|
||||
resolution: {integrity: sha512-4iZQtZNldhioTecSgi1LMR4E3uK5IcQ+EuWg4aAXmciOIHxPXPAHy7qSLuHqbzEiL1QP5G3MFwQnlVf/sJkFaQ==}
|
||||
|
||||
leven@3.1.0:
|
||||
resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==}
|
||||
|
@ -7811,7 +7811,7 @@ snapshots:
|
|||
|
||||
leac@0.6.0: {}
|
||||
|
||||
lemmy-js-client@0.20.0-alpha.7: {}
|
||||
lemmy-js-client@0.20.0-alpha.17: {}
|
||||
|
||||
leven@3.1.0: {}
|
||||
|
||||
|
|
|
@ -461,3 +461,7 @@ br.big {
|
|||
.totp-link {
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
em-emoji-picker {
|
||||
width: 100%;
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import App from "../shared/components/app/app";
|
|||
import { lazyHighlightjs } from "../shared/lazy-highlightjs";
|
||||
import { loadUserLanguage } from "../shared/services/I18NextService";
|
||||
import { verifyDynamicImports } from "../shared/dynamic-imports";
|
||||
import { setupEmojiDataModel } from "../shared/markdown";
|
||||
|
||||
import "bootstrap/js/dist/collapse";
|
||||
import "bootstrap/js/dist/dropdown";
|
||||
|
@ -22,7 +23,7 @@ async function startClient() {
|
|||
|
||||
lazyHighlightjs.enableLazyLoading();
|
||||
|
||||
await loadUserLanguage();
|
||||
await Promise.all([loadUserLanguage(), setupEmojiDataModel()]);
|
||||
|
||||
const wrapper = (
|
||||
<BrowserRouter>
|
||||
|
|
|
@ -47,13 +47,13 @@ export class CommentSortSelect extends Component<
|
|||
<option disabled aria-hidden="true">
|
||||
{I18NextService.i18n.t("sort_type")}
|
||||
</option>
|
||||
<option value={"Hot"}>{I18NextService.i18n.t("hot")}</option>,
|
||||
<option value={"Controversial"}>
|
||||
<option value="Hot">{I18NextService.i18n.t("hot")}</option>
|
||||
<option value="Controversial">
|
||||
{I18NextService.i18n.t("controversial")}
|
||||
</option>
|
||||
<option value={"Top"}>{I18NextService.i18n.t("top")}</option>,
|
||||
<option value={"New"}>{I18NextService.i18n.t("new")}</option>
|
||||
<option value={"Old"}>{I18NextService.i18n.t("old")}</option>
|
||||
<option value="Top">{I18NextService.i18n.t("top")}</option>
|
||||
<option value="New">{I18NextService.i18n.t("new")}</option>
|
||||
<option value="Old">{I18NextService.i18n.t("old")}</option>
|
||||
</select>
|
||||
<a
|
||||
className="sort-select-help text-muted"
|
||||
|
|
|
@ -5,6 +5,7 @@ interface PaginatorProps {
|
|||
page: number;
|
||||
onChange(val: number): any;
|
||||
nextDisabled: boolean;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export class Paginator extends Component<PaginatorProps, any> {
|
||||
|
@ -18,6 +19,7 @@ export class Paginator extends Component<PaginatorProps, any> {
|
|||
<button
|
||||
className="btn btn-secondary me-2"
|
||||
onClick={linkEvent(this, this.handlePrev)}
|
||||
disabled={this.props.disabled}
|
||||
>
|
||||
{I18NextService.i18n.t("prev")}
|
||||
</button>
|
||||
|
@ -26,6 +28,7 @@ export class Paginator extends Component<PaginatorProps, any> {
|
|||
<button
|
||||
className="btn btn-secondary"
|
||||
onClick={linkEvent(this, this.handleNext)}
|
||||
disabled={this.props.disabled}
|
||||
>
|
||||
{I18NextService.i18n.t("next")}
|
||||
</button>
|
||||
|
|
|
@ -1,19 +1,19 @@
|
|||
import { randomStr } from "@utils/helpers";
|
||||
import { Component, linkEvent } from "inferno";
|
||||
import { SortType } from "lemmy-js-client";
|
||||
import { PostSortType } from "lemmy-js-client";
|
||||
import { relTags, sortingHelpUrl } from "../../config";
|
||||
import { I18NextService } from "../../services";
|
||||
import { Icon } from "./icon";
|
||||
|
||||
interface SortSelectProps {
|
||||
sort: SortType;
|
||||
onChange(val: SortType): void;
|
||||
sort: PostSortType;
|
||||
onChange(val: PostSortType): void;
|
||||
hideHot?: boolean;
|
||||
hideMostComments?: boolean;
|
||||
}
|
||||
|
||||
interface SortSelectState {
|
||||
sort: SortType;
|
||||
sort: PostSortType;
|
||||
}
|
||||
|
||||
export class SortSelect extends Component<SortSelectProps, SortSelectState> {
|
||||
|
@ -47,55 +47,53 @@ export class SortSelect extends Component<SortSelectProps, SortSelectState> {
|
|||
{I18NextService.i18n.t("sort_type")}
|
||||
</option>
|
||||
{!this.props.hideHot && [
|
||||
<option key={"Hot"} value={"Hot"}>
|
||||
<option key="Hot" value="Hot">
|
||||
{I18NextService.i18n.t("hot")}
|
||||
</option>,
|
||||
<option key={"Active"} value={"Active"}>
|
||||
<option key="Active" value="Active">
|
||||
{I18NextService.i18n.t("active")}
|
||||
</option>,
|
||||
<option key={"Scaled"} value={"Scaled"}>
|
||||
<option key="Scaled" value="Scaled">
|
||||
{I18NextService.i18n.t("scaled")}
|
||||
</option>,
|
||||
]}
|
||||
<option value={"Controversial"}>
|
||||
<option value="Controversial">
|
||||
{I18NextService.i18n.t("controversial")}
|
||||
</option>
|
||||
<option value={"New"}>{I18NextService.i18n.t("new")}</option>
|
||||
<option value={"Old"}>{I18NextService.i18n.t("old")}</option>
|
||||
<option value="New">{I18NextService.i18n.t("new")}</option>
|
||||
<option value="Old">{I18NextService.i18n.t("old")}</option>
|
||||
{!this.props.hideMostComments && [
|
||||
<option key={"MostComments"} value={"MostComments"}>
|
||||
<option key="MostComments" value="MostComments">
|
||||
{I18NextService.i18n.t("most_comments")}
|
||||
</option>,
|
||||
<option key={"NewComments"} value={"NewComments"}>
|
||||
<option key="NewComments" value="NewComments">
|
||||
{I18NextService.i18n.t("new_comments")}
|
||||
</option>,
|
||||
]}
|
||||
<option disabled aria-hidden="true">
|
||||
─────
|
||||
</option>
|
||||
<option value={"TopHour"}>{I18NextService.i18n.t("top_hour")}</option>
|
||||
<option value={"TopSixHour"}>
|
||||
<option value="TopHour">{I18NextService.i18n.t("top_hour")}</option>
|
||||
<option value="TopSixHour">
|
||||
{I18NextService.i18n.t("top_six_hours")}
|
||||
</option>
|
||||
<option value={"TopTwelveHour"}>
|
||||
<option value="TopTwelveHour">
|
||||
{I18NextService.i18n.t("top_twelve_hours")}
|
||||
</option>
|
||||
<option value={"TopDay"}>{I18NextService.i18n.t("top_day")}</option>
|
||||
<option value={"TopWeek"}>{I18NextService.i18n.t("top_week")}</option>
|
||||
<option value={"TopMonth"}>
|
||||
{I18NextService.i18n.t("top_month")}
|
||||
</option>
|
||||
<option value={"TopThreeMonths"}>
|
||||
<option value="TopDay">{I18NextService.i18n.t("top_day")}</option>
|
||||
<option value="TopWeek">{I18NextService.i18n.t("top_week")}</option>
|
||||
<option value="TopMonth">{I18NextService.i18n.t("top_month")}</option>
|
||||
<option value="TopThreeMonths">
|
||||
{I18NextService.i18n.t("top_three_months")}
|
||||
</option>
|
||||
<option value={"TopSixMonths"}>
|
||||
<option value="TopSixMonths">
|
||||
{I18NextService.i18n.t("top_six_months")}
|
||||
</option>
|
||||
<option value={"TopNineMonths"}>
|
||||
<option value="TopNineMonths">
|
||||
{I18NextService.i18n.t("top_nine_months")}
|
||||
</option>
|
||||
<option value={"TopYear"}>{I18NextService.i18n.t("top_year")}</option>
|
||||
<option value={"TopAll"}>{I18NextService.i18n.t("top_all")}</option>
|
||||
<option value="TopYear">{I18NextService.i18n.t("top_year")}</option>
|
||||
<option value="TopAll">{I18NextService.i18n.t("top_all")}</option>
|
||||
</select>
|
||||
<a
|
||||
className="sort-select-icon text-muted"
|
||||
|
|
|
@ -16,7 +16,7 @@ import {
|
|||
ListCommunities,
|
||||
ListCommunitiesResponse,
|
||||
ListingType,
|
||||
SortType,
|
||||
PostSortType,
|
||||
} from "lemmy-js-client";
|
||||
import { InitialFetchRequest } from "../../interfaces";
|
||||
import { FirstLoadService, I18NextService } from "../../services";
|
||||
|
@ -55,7 +55,7 @@ interface CommunitiesState {
|
|||
|
||||
interface CommunitiesProps {
|
||||
listingType: ListingType;
|
||||
sort: SortType;
|
||||
sort: PostSortType;
|
||||
page: number;
|
||||
}
|
||||
|
||||
|
@ -63,8 +63,8 @@ function getListingTypeFromQuery(listingType?: string): ListingType {
|
|||
return listingType ? (listingType as ListingType) : "Local";
|
||||
}
|
||||
|
||||
function getSortTypeFromQuery(type?: string): SortType {
|
||||
return type ? (type as SortType) : "TopMonth";
|
||||
function getSortTypeFromQuery(type?: string): PostSortType {
|
||||
return type ? (type as PostSortType) : "TopMonth";
|
||||
}
|
||||
|
||||
export function getCommunitiesQueryParams(source?: string): CommunitiesProps {
|
||||
|
@ -302,7 +302,7 @@ export class Communities extends Component<
|
|||
this.updateUrl({ page });
|
||||
}
|
||||
|
||||
handleSortChange(val: SortType) {
|
||||
handleSortChange(val: PostSortType) {
|
||||
this.updateUrl({ sort: val, page: 1 });
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import {
|
||||
commentsToFlatNodes,
|
||||
commentToPostSortType,
|
||||
communityRSSUrl,
|
||||
editComment,
|
||||
editPost,
|
||||
|
@ -81,9 +82,10 @@ import {
|
|||
RemovePost,
|
||||
SaveComment,
|
||||
SavePost,
|
||||
SortType,
|
||||
PostSortType,
|
||||
SuccessResponse,
|
||||
TransferCommunity,
|
||||
CommentSortType,
|
||||
} from "lemmy-js-client";
|
||||
import { fetchLimit, relTags } from "../../config";
|
||||
import {
|
||||
|
@ -121,6 +123,7 @@ import { IRoutePropsWithFetch } from "../../routes";
|
|||
import PostHiddenSelect from "../common/post-hidden-select";
|
||||
import { isBrowser } from "@utils/browser";
|
||||
import { LoadingEllipses } from "../common/loading-ellipses";
|
||||
import { CommentSortSelect } from "../common/comment-sort-select";
|
||||
|
||||
type CommunityData = RouteDataResponse<{
|
||||
communityRes: GetCommunityResponse;
|
||||
|
@ -139,12 +142,12 @@ interface State {
|
|||
|
||||
interface CommunityProps {
|
||||
dataType: DataType;
|
||||
sort: SortType;
|
||||
sort: PostSortType;
|
||||
pageCursor?: PaginationCursor;
|
||||
showHidden?: StringBoolean;
|
||||
}
|
||||
|
||||
type Fallbacks = { sort: SortType };
|
||||
type Fallbacks = { sort: PostSortType };
|
||||
|
||||
export function getCommunityQueryParams(
|
||||
source: string | undefined,
|
||||
|
@ -162,7 +165,8 @@ export function getCommunityQueryParams(
|
|||
},
|
||||
source,
|
||||
{
|
||||
sort: local_user?.default_sort_type ?? local_site.default_sort_type,
|
||||
sort:
|
||||
local_user?.default_post_sort_type ?? local_site.default_post_sort_type,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
@ -171,12 +175,11 @@ function getDataTypeFromQuery(type?: string): DataType {
|
|||
return type ? DataType[type] : DataType.Post;
|
||||
}
|
||||
|
||||
function getSortTypeFromQuery(type?: string): SortType {
|
||||
const mySortType =
|
||||
UserService.Instance.myUserInfo?.local_user_view.local_user
|
||||
.default_sort_type;
|
||||
|
||||
return type ? (type as SortType) : (mySortType ?? "Active");
|
||||
function getSortTypeFromQuery(
|
||||
type: string | undefined,
|
||||
fallback: PostSortType,
|
||||
): PostSortType {
|
||||
return type ? (type as PostSortType) : fallback;
|
||||
}
|
||||
|
||||
type CommunityPathProps = { name: string };
|
||||
|
@ -215,6 +218,7 @@ export class Community extends Component<CommunityRouteProps, State> {
|
|||
super(props, context);
|
||||
|
||||
this.handleSortChange = this.handleSortChange.bind(this);
|
||||
this.handleCommentSortChange = this.handleCommentSortChange.bind(this);
|
||||
this.handleDataTypeChange = this.handleDataTypeChange.bind(this);
|
||||
this.handlePageNext = this.handlePageNext.bind(this);
|
||||
this.handlePagePrev = this.handlePagePrev.bind(this);
|
||||
|
@ -628,7 +632,14 @@ export class Community extends Component<CommunityRouteProps, State> {
|
|||
</span>
|
||||
)}
|
||||
<span className="me-2">
|
||||
<SortSelect sort={sort} onChange={this.handleSortChange} />
|
||||
{this.props.dataType === DataType.Post ? (
|
||||
<SortSelect sort={sort} onChange={this.handleSortChange} />
|
||||
) : (
|
||||
<CommentSortSelect
|
||||
sort={postToCommentSortType(sort)}
|
||||
onChange={this.handleCommentSortChange}
|
||||
/>
|
||||
)}
|
||||
</span>
|
||||
{communityRss && (
|
||||
<>
|
||||
|
@ -654,8 +665,18 @@ export class Community extends Component<CommunityRouteProps, State> {
|
|||
this.updateUrl({ pageCursor: nextPage });
|
||||
}
|
||||
|
||||
handleSortChange(sort: SortType) {
|
||||
this.updateUrl({ sort, pageCursor: undefined });
|
||||
handleSortChange(sort: PostSortType) {
|
||||
this.updateUrl({
|
||||
sort: sort,
|
||||
pageCursor: undefined,
|
||||
});
|
||||
}
|
||||
|
||||
handleCommentSortChange(sort: CommentSortType) {
|
||||
this.updateUrl({
|
||||
sort: commentToPostSortType(sort),
|
||||
pageCursor: undefined,
|
||||
});
|
||||
}
|
||||
|
||||
handleDataTypeChange(dataType: DataType) {
|
||||
|
|
|
@ -6,9 +6,6 @@ import classNames from "classnames";
|
|||
import { Component } from "inferno";
|
||||
import {
|
||||
BannedPersonsResponse,
|
||||
CreateCustomEmoji,
|
||||
DeleteCustomEmoji,
|
||||
EditCustomEmoji,
|
||||
EditSite,
|
||||
GetFederatedInstancesResponse,
|
||||
GetSiteResponse,
|
||||
|
@ -17,7 +14,6 @@ import {
|
|||
PersonView,
|
||||
} from "lemmy-js-client";
|
||||
import { InitialFetchRequest } from "../../interfaces";
|
||||
import { removeFromEmojiDataModel, updateEmojiDataModel } from "../../markdown";
|
||||
import { FirstLoadService, I18NextService } from "../../services";
|
||||
import {
|
||||
EMPTY_REQUEST,
|
||||
|
@ -104,9 +100,6 @@ export class AdminSettings extends Component<
|
|||
super(props, context);
|
||||
|
||||
this.handleEditSite = this.handleEditSite.bind(this);
|
||||
this.handleEditEmoji = this.handleEditEmoji.bind(this);
|
||||
this.handleDeleteEmoji = this.handleDeleteEmoji.bind(this);
|
||||
this.handleCreateEmoji = this.handleCreateEmoji.bind(this);
|
||||
this.handleUploadsPageChange = this.handleUploadsPageChange.bind(this);
|
||||
this.handleToggleShowLeaveAdminConfirmation =
|
||||
this.handleToggleShowLeaveAdminConfirmation.bind(this);
|
||||
|
@ -249,11 +242,7 @@ export class AdminSettings extends Component<
|
|||
id="taglines-tab-pane"
|
||||
>
|
||||
<div className="row">
|
||||
<TaglineForm
|
||||
taglines={this.state.siteRes.taglines}
|
||||
onSaveSite={this.handleEditSite}
|
||||
loading={this.state.loading}
|
||||
/>
|
||||
<TaglineForm />
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
|
@ -270,11 +259,7 @@ export class AdminSettings extends Component<
|
|||
id="emojis-tab-pane"
|
||||
>
|
||||
<div className="row">
|
||||
<EmojiForm
|
||||
onCreate={this.handleCreateEmoji}
|
||||
onDelete={this.handleDeleteEmoji}
|
||||
onEdit={this.handleEditEmoji}
|
||||
/>
|
||||
<EmojiForm />
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
|
@ -431,7 +416,6 @@ export class AdminSettings extends Component<
|
|||
this.setState(s => {
|
||||
s.siteRes.site_view = editRes.data.site_view;
|
||||
// TODO: Where to get taglines from?
|
||||
s.siteRes.taglines = editRes.data.taglines;
|
||||
return s;
|
||||
});
|
||||
toast(I18NextService.i18n.t("site_saved"));
|
||||
|
@ -464,27 +448,6 @@ export class AdminSettings extends Component<
|
|||
}
|
||||
}
|
||||
|
||||
async handleEditEmoji(form: EditCustomEmoji) {
|
||||
const res = await HttpService.client.editCustomEmoji(form);
|
||||
if (res.state === "success") {
|
||||
updateEmojiDataModel(res.data.custom_emoji);
|
||||
}
|
||||
}
|
||||
|
||||
async handleDeleteEmoji(form: DeleteCustomEmoji) {
|
||||
const res = await HttpService.client.deleteCustomEmoji(form);
|
||||
if (res.state === "success") {
|
||||
removeFromEmojiDataModel(form.id);
|
||||
}
|
||||
}
|
||||
|
||||
async handleCreateEmoji(form: CreateCustomEmoji) {
|
||||
const res = await HttpService.client.createCustomEmoji(form);
|
||||
if (res.state === "success") {
|
||||
updateEmojiDataModel(res.data.custom_emoji);
|
||||
}
|
||||
}
|
||||
|
||||
async handleUploadsPageChange(val: number) {
|
||||
this.setState({ uploadsPage: val });
|
||||
snapToTop();
|
||||
|
|
|
@ -1,79 +1,81 @@
|
|||
import { setIsoData } from "@utils/app";
|
||||
import { capitalizeFirstLetter } from "@utils/helpers";
|
||||
import { Component, linkEvent } from "inferno";
|
||||
import {
|
||||
CreateCustomEmoji,
|
||||
DeleteCustomEmoji,
|
||||
EditCustomEmoji,
|
||||
GetSiteResponse,
|
||||
} from "lemmy-js-client";
|
||||
import { customEmojisLookup } from "../../markdown";
|
||||
import { CustomEmojiView } from "lemmy-js-client";
|
||||
import { emojiMartCategories, EmojiMartCategory } from "../../markdown";
|
||||
import { HttpService, I18NextService } from "../../services";
|
||||
import { pictrsDeleteToast, toast } from "../../toast";
|
||||
import { EmojiMart } from "../common/emoji-mart";
|
||||
import { Icon, Spinner } from "../common/icon";
|
||||
import { Paginator } from "../common/paginator";
|
||||
import { tippyMixin } from "../mixins/tippy-mixin";
|
||||
import { isBrowser } from "@utils/browser";
|
||||
import classNames from "classnames";
|
||||
import { amAdmin } from "@utils/roles";
|
||||
import { Prompt } from "inferno-router";
|
||||
|
||||
interface EmojiFormProps {
|
||||
onEdit(form: EditCustomEmoji): void;
|
||||
onCreate(form: CreateCustomEmoji): void;
|
||||
onDelete(form: DeleteCustomEmoji): void;
|
||||
interface EditableEmoji {
|
||||
change?: "update" | "delete" | "create";
|
||||
emoji: CustomEmojiView;
|
||||
loading?: boolean;
|
||||
}
|
||||
|
||||
function markForUpdate(editable: EditableEmoji) {
|
||||
if (editable.change !== "create") {
|
||||
editable.change = "update";
|
||||
}
|
||||
}
|
||||
|
||||
interface EmojiFormState {
|
||||
siteRes: GetSiteResponse;
|
||||
customEmojis: CustomEmojiViewForm[];
|
||||
page: number;
|
||||
}
|
||||
|
||||
interface CustomEmojiViewForm {
|
||||
id: number;
|
||||
category: string;
|
||||
shortcode: string;
|
||||
image_url: string;
|
||||
alt_text: string;
|
||||
keywords: string;
|
||||
changed: boolean;
|
||||
emojis: EditableEmoji[]; // Emojis for the current page
|
||||
allEmojis: CustomEmojiView[]; // All emojis for emoji lookup across pages
|
||||
emojiMartCustom: EmojiMartCategory[];
|
||||
emojiMartKey: number;
|
||||
page: number;
|
||||
loading: boolean;
|
||||
}
|
||||
|
||||
@tippyMixin
|
||||
export class EmojiForm extends Component<EmojiFormProps, EmojiFormState> {
|
||||
private isoData = setIsoData(this.context);
|
||||
export class EmojiForm extends Component<Record<never, never>, EmojiFormState> {
|
||||
private itemsPerPage = 15;
|
||||
private emptyState: EmojiFormState = {
|
||||
siteRes: this.isoData.site_res,
|
||||
customEmojis: this.isoData.site_res.custom_emojis.map((x, index) => ({
|
||||
id: x.custom_emoji.id,
|
||||
category: x.custom_emoji.category,
|
||||
shortcode: x.custom_emoji.shortcode,
|
||||
image_url: x.custom_emoji.image_url,
|
||||
alt_text: x.custom_emoji.alt_text,
|
||||
keywords: x.keywords.map(x => x.keyword).join(" "),
|
||||
changed: false,
|
||||
page: 1 + Math.floor(index / this.itemsPerPage),
|
||||
loading: false,
|
||||
})),
|
||||
private needsRefetch = true;
|
||||
state: EmojiFormState = {
|
||||
emojis: [],
|
||||
allEmojis: [],
|
||||
emojiMartCustom: [],
|
||||
emojiMartKey: 1,
|
||||
loading: false,
|
||||
page: 1,
|
||||
};
|
||||
state: EmojiFormState;
|
||||
private scrollRef: any = {};
|
||||
constructor(props: any, context: any) {
|
||||
super(props, context);
|
||||
this.state = this.emptyState;
|
||||
|
||||
this.handlePageChange = this.handlePageChange.bind(this);
|
||||
this.handleEmojiClick = this.handleEmojiClick.bind(this);
|
||||
}
|
||||
|
||||
async componentWillMount() {
|
||||
if (isBrowser()) {
|
||||
this.handlePageChange(1);
|
||||
}
|
||||
}
|
||||
|
||||
hasPendingChanges() {
|
||||
return this.state.emojis.some(x => x.change);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="home-emojis-form col-12">
|
||||
<Prompt
|
||||
message={I18NextService.i18n.t("block_leaving")}
|
||||
when={this.hasPendingChanges()}
|
||||
/>
|
||||
<h1 className="h4 mb-4">{I18NextService.i18n.t("custom_emojis")}</h1>
|
||||
{customEmojisLookup.size > 0 && (
|
||||
{this.state.emojiMartCustom.length > 0 && (
|
||||
<div>
|
||||
<EmojiMart
|
||||
key={this.state.emojiMartKey}
|
||||
onEmojiClick={this.handleEmojiClick}
|
||||
pickerOptions={this.configurePicker()}
|
||||
></EmojiMart>
|
||||
|
@ -87,6 +89,10 @@ export class EmojiForm extends Component<EmojiFormProps, EmojiFormState> {
|
|||
<thead className="pointer">
|
||||
<tr>
|
||||
<th>{I18NextService.i18n.t("column_emoji")}</th>
|
||||
<th
|
||||
className="text-right"
|
||||
// Upload button
|
||||
/>
|
||||
<th className="text-right">
|
||||
{I18NextService.i18n.t("column_shortcode")}
|
||||
</th>
|
||||
|
@ -102,20 +108,15 @@ export class EmojiForm extends Component<EmojiFormProps, EmojiFormState> {
|
|||
<th className="text-right d-lg-table-cell">
|
||||
{I18NextService.i18n.t("column_keywords")}
|
||||
</th>
|
||||
<th></th>
|
||||
<th style="width:121px"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{this.state.customEmojis
|
||||
.slice(
|
||||
Number((this.state.page - 1) * this.itemsPerPage),
|
||||
Number(
|
||||
(this.state.page - 1) * this.itemsPerPage +
|
||||
this.itemsPerPage,
|
||||
),
|
||||
)
|
||||
.map((cv, index) => (
|
||||
<tr key={index} ref={e => (this.scrollRef[cv.shortcode] = e)}>
|
||||
{this.state.emojis.map((editable: EditableEmoji, index) => {
|
||||
const cv = editable.emoji.custom_emoji;
|
||||
return (
|
||||
<tr key={index}>
|
||||
<td style="text-align:center;">
|
||||
{cv.image_url.length > 0 && (
|
||||
<img
|
||||
|
@ -124,7 +125,9 @@ export class EmojiForm extends Component<EmojiFormProps, EmojiFormState> {
|
|||
alt={cv.alt_text}
|
||||
/>
|
||||
)}
|
||||
{cv.image_url.length === 0 && (
|
||||
</td>
|
||||
<td>
|
||||
{
|
||||
<label
|
||||
// TODO: Fix this linting violation
|
||||
// eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex
|
||||
|
@ -150,7 +153,7 @@ export class EmojiForm extends Component<EmojiFormProps, EmojiFormState> {
|
|||
)}
|
||||
/>
|
||||
</label>
|
||||
)}
|
||||
}
|
||||
</td>
|
||||
<td className="text-right">
|
||||
<input
|
||||
|
@ -167,6 +170,7 @@ export class EmojiForm extends Component<EmojiFormProps, EmojiFormState> {
|
|||
</td>
|
||||
<td className="text-right">
|
||||
<input
|
||||
ref={e => (this.scrollRef[cv.shortcode] = e)}
|
||||
type="text"
|
||||
placeholder="Category"
|
||||
className="form-control"
|
||||
|
@ -206,31 +210,54 @@ export class EmojiForm extends Component<EmojiFormProps, EmojiFormState> {
|
|||
type="text"
|
||||
placeholder="Keywords"
|
||||
className="form-control"
|
||||
value={cv.keywords}
|
||||
value={editable.emoji.keywords
|
||||
.map(k => k.keyword)
|
||||
.join(" ")}
|
||||
onInput={linkEvent(
|
||||
{ form: this, index: index },
|
||||
this.handleEmojiKeywordChange,
|
||||
)}
|
||||
/>
|
||||
</td>
|
||||
<td
|
||||
className={classNames("", {
|
||||
"border-info": editable.change === "update",
|
||||
"border-danger": editable.change === "delete",
|
||||
"border-warning": editable.change === "create",
|
||||
})}
|
||||
>
|
||||
{editable.change === "update" && (
|
||||
<span>
|
||||
<Icon icon="transfer" />
|
||||
</span>
|
||||
)}
|
||||
{editable.change === "delete" && (
|
||||
<span>
|
||||
<Icon icon="trash" />
|
||||
</span>
|
||||
)}
|
||||
{editable.change === "create" && (
|
||||
<span>
|
||||
<Icon icon="add" />
|
||||
</span>
|
||||
)}
|
||||
</td>
|
||||
<td>
|
||||
<div>
|
||||
<span title={this.getEditTooltip(cv)}>
|
||||
<div class="row flex-nowrap g-0">
|
||||
<span class="col" title={this.getEditTooltip(editable)}>
|
||||
<button
|
||||
className={
|
||||
(this.canEdit(cv)
|
||||
? "text-success "
|
||||
: "text-muted ") + "btn btn-link btn-animate"
|
||||
}
|
||||
className={classNames("btn btn-link btn-animate", {
|
||||
"text-success": this.canSave(editable),
|
||||
})}
|
||||
onClick={linkEvent(
|
||||
{ i: this, cv: cv },
|
||||
this.handleEditEmojiClick,
|
||||
{ i: this, cv: editable },
|
||||
this.handleSaveEmojiClick,
|
||||
)}
|
||||
data-tippy-content={I18NextService.i18n.t("save")}
|
||||
aria-label={I18NextService.i18n.t("save")}
|
||||
disabled={!this.canEdit(cv)}
|
||||
disabled={!this.canSave(editable)}
|
||||
>
|
||||
{cv.loading ? (
|
||||
{editable.loading ? (
|
||||
<Spinner />
|
||||
) : (
|
||||
capitalizeFirstLetter(
|
||||
|
@ -240,14 +267,14 @@ export class EmojiForm extends Component<EmojiFormProps, EmojiFormState> {
|
|||
</button>
|
||||
</span>
|
||||
<button
|
||||
className="btn btn-link btn-animate text-muted"
|
||||
className="col btn btn-link btn-animate text-muted"
|
||||
onClick={linkEvent(
|
||||
{ i: this, index: index, cv: cv },
|
||||
{ i: this, index: index, cv: editable },
|
||||
this.handleDeleteEmojiClick,
|
||||
)}
|
||||
data-tippy-content={I18NextService.i18n.t("delete")}
|
||||
aria-label={I18NextService.i18n.t("delete")}
|
||||
disabled={cv.loading}
|
||||
disabled={editable.loading}
|
||||
title={I18NextService.i18n.t("delete")}
|
||||
>
|
||||
<Icon
|
||||
|
@ -255,10 +282,28 @@ export class EmojiForm extends Component<EmojiFormProps, EmojiFormState> {
|
|||
classes="icon-inline text-danger"
|
||||
/>
|
||||
</button>
|
||||
<button
|
||||
className={classNames(
|
||||
"col btn btn-link btn-animate",
|
||||
{
|
||||
"text-danger": !!editable.change,
|
||||
},
|
||||
)}
|
||||
onClick={linkEvent(
|
||||
{ i: this, cv: editable },
|
||||
this.handleCancelEmojiClick,
|
||||
)}
|
||||
data-tippy-content={I18NextService.i18n.t("cancel")}
|
||||
aria-label={I18NextService.i18n.t("cancel")}
|
||||
disabled={!editable.change}
|
||||
>
|
||||
{I18NextService.i18n.t("cancel")}
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
<br />
|
||||
|
@ -273,43 +318,91 @@ export class EmojiForm extends Component<EmojiFormProps, EmojiFormState> {
|
|||
page={this.state.page}
|
||||
onChange={this.handlePageChange}
|
||||
nextDisabled={false}
|
||||
disabled={this.hasPendingChanges()}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
canEdit(cv: CustomEmojiViewForm) {
|
||||
const noEmptyFields =
|
||||
cv.alt_text.length > 0 &&
|
||||
cv.category.length > 0 &&
|
||||
cv.image_url.length > 0 &&
|
||||
cv.shortcode.length > 0;
|
||||
const noDuplicateShortCodes =
|
||||
this.state.customEmojis.filter(
|
||||
x => x.shortcode === cv.shortcode && x.id !== cv.id,
|
||||
).length === 0;
|
||||
return noEmptyFields && noDuplicateShortCodes && !cv.loading && cv.changed;
|
||||
canSave(cv: EditableEmoji) {
|
||||
const requiredFields =
|
||||
cv.emoji.custom_emoji.image_url.length > 0 &&
|
||||
cv.emoji.custom_emoji.shortcode.length > 0;
|
||||
return requiredFields && !cv.loading;
|
||||
}
|
||||
|
||||
getEditTooltip(cv: CustomEmojiViewForm) {
|
||||
if (this.canEdit(cv)) return I18NextService.i18n.t("save");
|
||||
getEditTooltip(cv: EditableEmoji) {
|
||||
if (this.canSave(cv)) return I18NextService.i18n.t("save");
|
||||
else return I18NextService.i18n.t("custom_emoji_save_validation");
|
||||
}
|
||||
|
||||
handlePageChange(page: number) {
|
||||
this.setState({ page: page });
|
||||
async handlePageChange(page: number) {
|
||||
this.setState({ loading: true });
|
||||
let allEmojis: CustomEmojiView[] = this.state.allEmojis;
|
||||
let emojiMartCustom: EmojiMartCategory[] = this.state.emojiMartCustom;
|
||||
let emojiMartKey: number = this.state.emojiMartKey;
|
||||
if (this.needsRefetch) {
|
||||
const emojiRes = await HttpService.client.listCustomEmojis({
|
||||
ignore_page_limits: true,
|
||||
});
|
||||
if (emojiRes.state === "success") {
|
||||
this.needsRefetch = false;
|
||||
allEmojis = emojiRes.data.custom_emojis;
|
||||
allEmojis.sort((a, b) => {
|
||||
const categoryOrder = a.custom_emoji.category.localeCompare(
|
||||
b.custom_emoji.category,
|
||||
);
|
||||
if (categoryOrder === 0) {
|
||||
return a.custom_emoji.shortcode.localeCompare(
|
||||
b.custom_emoji.shortcode,
|
||||
);
|
||||
}
|
||||
return categoryOrder;
|
||||
});
|
||||
}
|
||||
emojiMartCustom = emojiMartCategories(allEmojis);
|
||||
emojiMartKey++;
|
||||
}
|
||||
if (allEmojis) {
|
||||
const startIndex = (page - 1) * this.itemsPerPage;
|
||||
const emojis = allEmojis
|
||||
.slice(startIndex, startIndex + this.itemsPerPage)
|
||||
.map(x => ({ emoji: structuredClone(x) })); // clone for restore after cancel
|
||||
this.setState({
|
||||
loading: false,
|
||||
allEmojis,
|
||||
emojiMartCustom,
|
||||
emojiMartKey,
|
||||
emojis,
|
||||
page,
|
||||
});
|
||||
} else {
|
||||
this.setState({ loading: false, page });
|
||||
}
|
||||
}
|
||||
|
||||
handleEmojiClick(e: any) {
|
||||
const view = customEmojisLookup.get(e.id);
|
||||
if (view) {
|
||||
const page = this.state.customEmojis.find(
|
||||
x => x.id === view.custom_emoji.id,
|
||||
)?.page;
|
||||
if (page) {
|
||||
this.setState({ page: page });
|
||||
this.scrollRef[view.custom_emoji.shortcode].scrollIntoView();
|
||||
async handleEmojiClick(e: any) {
|
||||
const emojiIndex = this.state.allEmojis.findIndex(
|
||||
x => x.custom_emoji.shortcode === e.id,
|
||||
);
|
||||
if (emojiIndex >= 0) {
|
||||
const { shortcode } = this.state.allEmojis[emojiIndex].custom_emoji;
|
||||
const page = Math.floor(emojiIndex / this.itemsPerPage) + 1;
|
||||
if (page !== this.state.page) {
|
||||
if (
|
||||
this.hasPendingChanges() &&
|
||||
!confirm(I18NextService.i18n.t("block_leaving"))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
await this.handlePageChange(page);
|
||||
await new Promise(r => setTimeout(r));
|
||||
}
|
||||
if (shortcode) {
|
||||
const categoryInput: HTMLInputElement | undefined =
|
||||
this.scrollRef[shortcode];
|
||||
categoryInput?.focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -318,32 +411,22 @@ export class EmojiForm extends Component<EmojiFormProps, EmojiFormState> {
|
|||
props: { form: EmojiForm; index: number },
|
||||
event: any,
|
||||
) {
|
||||
const custom_emojis = [...props.form.state.customEmojis];
|
||||
const pagedIndex =
|
||||
(props.form.state.page - 1) * props.form.itemsPerPage + props.index;
|
||||
const item = {
|
||||
...props.form.state.customEmojis[pagedIndex],
|
||||
category: event.target.value,
|
||||
changed: true,
|
||||
};
|
||||
custom_emojis[Number(pagedIndex)] = item;
|
||||
props.form.setState({ customEmojis: custom_emojis });
|
||||
const editable: EditableEmoji = props.form.state.emojis[props.index];
|
||||
props.form.setState(() => {
|
||||
markForUpdate(editable);
|
||||
editable.emoji.custom_emoji.category = event.target.value;
|
||||
});
|
||||
}
|
||||
|
||||
handleEmojiShortCodeChange(
|
||||
props: { form: EmojiForm; index: number },
|
||||
event: any,
|
||||
) {
|
||||
const custom_emojis = [...props.form.state.customEmojis];
|
||||
const pagedIndex =
|
||||
(props.form.state.page - 1) * props.form.itemsPerPage + props.index;
|
||||
const item = {
|
||||
...props.form.state.customEmojis[pagedIndex],
|
||||
shortcode: event.target.value,
|
||||
changed: true,
|
||||
};
|
||||
custom_emojis[Number(pagedIndex)] = item;
|
||||
props.form.setState({ customEmojis: custom_emojis });
|
||||
const editable: EditableEmoji = props.form.state.emojis[props.index];
|
||||
props.form.setState(() => {
|
||||
markForUpdate(editable);
|
||||
editable.emoji.custom_emoji.shortcode = event.target.value;
|
||||
});
|
||||
}
|
||||
|
||||
handleEmojiImageUrlChange(
|
||||
|
@ -354,28 +437,11 @@ export class EmojiForm extends Component<EmojiFormProps, EmojiFormState> {
|
|||
}: { form: EmojiForm; index: number; overrideValue: string | null },
|
||||
event: any,
|
||||
) {
|
||||
form.setState(prevState => {
|
||||
const custom_emojis = [...form.state.customEmojis];
|
||||
const pagedIndex = (form.state.page - 1) * form.itemsPerPage + index;
|
||||
const item = {
|
||||
...form.state.customEmojis[pagedIndex],
|
||||
image_url: overrideValue ?? event.target.value,
|
||||
changed: true,
|
||||
};
|
||||
custom_emojis[Number(pagedIndex)] = item;
|
||||
return {
|
||||
...prevState,
|
||||
customEmojis: prevState.customEmojis.map((ce, i) =>
|
||||
i === pagedIndex
|
||||
? {
|
||||
...ce,
|
||||
image_url: overrideValue ?? event.target.value,
|
||||
changed: true,
|
||||
loading: false,
|
||||
}
|
||||
: ce,
|
||||
),
|
||||
};
|
||||
const editable: EditableEmoji = form.state.emojis[index];
|
||||
form.setState(() => {
|
||||
markForUpdate(editable);
|
||||
editable.emoji.custom_emoji.image_url =
|
||||
overrideValue ?? event.target.value;
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -383,97 +449,117 @@ export class EmojiForm extends Component<EmojiFormProps, EmojiFormState> {
|
|||
props: { form: EmojiForm; index: number },
|
||||
event: any,
|
||||
) {
|
||||
const custom_emojis = [...props.form.state.customEmojis];
|
||||
const pagedIndex =
|
||||
(props.form.state.page - 1) * props.form.itemsPerPage + props.index;
|
||||
const item = {
|
||||
...props.form.state.customEmojis[pagedIndex],
|
||||
alt_text: event.target.value,
|
||||
changed: true,
|
||||
};
|
||||
custom_emojis[Number(pagedIndex)] = item;
|
||||
props.form.setState({ customEmojis: custom_emojis });
|
||||
const editable: EditableEmoji = props.form.state.emojis[props.index];
|
||||
props.form.setState(() => {
|
||||
markForUpdate(editable);
|
||||
editable.emoji.custom_emoji.alt_text = event.target.value;
|
||||
});
|
||||
}
|
||||
|
||||
handleEmojiKeywordChange(
|
||||
props: { form: EmojiForm; index: number },
|
||||
event: any,
|
||||
) {
|
||||
const custom_emojis = [...props.form.state.customEmojis];
|
||||
const pagedIndex =
|
||||
(props.form.state.page - 1) * props.form.itemsPerPage + props.index;
|
||||
const item = {
|
||||
...props.form.state.customEmojis[pagedIndex],
|
||||
keywords: event.target.value,
|
||||
changed: true,
|
||||
};
|
||||
custom_emojis[Number(pagedIndex)] = item;
|
||||
props.form.setState({ customEmojis: custom_emojis });
|
||||
const editable: EditableEmoji = props.form.state.emojis[props.index];
|
||||
props.form.setState(() => {
|
||||
markForUpdate(editable);
|
||||
editable.emoji.keywords = event.target.value
|
||||
.split(" ")
|
||||
.map((x: string) => ({ id: -1, keyword: x }));
|
||||
});
|
||||
}
|
||||
|
||||
handleDeleteEmojiClick(d: {
|
||||
i: EmojiForm;
|
||||
index: number;
|
||||
cv: CustomEmojiViewForm;
|
||||
cv: EditableEmoji;
|
||||
}) {
|
||||
const pagedIndex = (d.i.state.page - 1) * d.i.itemsPerPage + d.index;
|
||||
if (d.cv.id !== 0) {
|
||||
d.i.props.onDelete({
|
||||
id: d.cv.id,
|
||||
});
|
||||
if (d.cv.change === "create") {
|
||||
// This drops the entry immediately, other deletes have to be saved.
|
||||
d.i.setState(prev => ({
|
||||
emojis: prev.emojis.filter(x => x !== d.cv),
|
||||
}));
|
||||
} else {
|
||||
const custom_emojis = [...d.i.state.customEmojis];
|
||||
custom_emojis.splice(Number(pagedIndex), 1);
|
||||
d.i.setState({ customEmojis: custom_emojis });
|
||||
}
|
||||
}
|
||||
|
||||
handleEditEmojiClick(d: { i: EmojiForm; cv: CustomEmojiViewForm }) {
|
||||
const keywords = d.cv.keywords
|
||||
.split(" ")
|
||||
.filter(x => x.length > 0) as string[];
|
||||
const uniqueKeywords = Array.from(new Set(keywords));
|
||||
if (d.cv.id !== 0) {
|
||||
d.i.props.onEdit({
|
||||
id: d.cv.id,
|
||||
category: d.cv.category,
|
||||
image_url: d.cv.image_url,
|
||||
alt_text: d.cv.alt_text,
|
||||
keywords: uniqueKeywords,
|
||||
});
|
||||
} else {
|
||||
d.i.props.onCreate({
|
||||
category: d.cv.category,
|
||||
shortcode: d.cv.shortcode,
|
||||
image_url: d.cv.image_url,
|
||||
alt_text: d.cv.alt_text,
|
||||
keywords: uniqueKeywords,
|
||||
d.i.setState(() => {
|
||||
d.cv.change = "delete";
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
handleAddEmojiClick(form: EmojiForm, event: any) {
|
||||
async handleSaveEmojiClick(d: { i: EmojiForm; cv: EditableEmoji }) {
|
||||
d.i.needsRefetch = true;
|
||||
const editable = d.cv;
|
||||
if (editable.change === "update") {
|
||||
const resp = await HttpService.client.editCustomEmoji({
|
||||
...editable.emoji.custom_emoji,
|
||||
keywords: editable.emoji.keywords.map(x => x.keyword),
|
||||
});
|
||||
if (resp.state === "success") {
|
||||
d.i.setState(() => {
|
||||
editable.emoji = resp.data.custom_emoji;
|
||||
editable.change = undefined;
|
||||
});
|
||||
}
|
||||
} else if (editable.change === "delete") {
|
||||
const resp = await HttpService.client.deleteCustomEmoji(
|
||||
editable.emoji.custom_emoji,
|
||||
);
|
||||
if (resp.state === "success") {
|
||||
d.i.setState(prev => ({
|
||||
emojis: prev.emojis.filter(x => x !== editable),
|
||||
}));
|
||||
}
|
||||
} else if (editable.change === "create") {
|
||||
const resp = await HttpService.client.createCustomEmoji({
|
||||
...editable.emoji.custom_emoji,
|
||||
keywords: editable.emoji.keywords.map(x => x.keyword),
|
||||
});
|
||||
if (resp.state === "success") {
|
||||
d.i.setState(() => {
|
||||
editable.emoji = resp.data.custom_emoji;
|
||||
editable.change = undefined;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async handleCancelEmojiClick(d: { i: EmojiForm; cv: EditableEmoji }) {
|
||||
if (d.cv.change === "create") {
|
||||
d.i.setState(() => {
|
||||
return {
|
||||
emojis: d.i.state.emojis.filter(x => x !== d.cv),
|
||||
};
|
||||
});
|
||||
} else if (d.cv.change === "update" || d.cv.change === "delete") {
|
||||
const original = d.i.state.allEmojis.find(
|
||||
x => x.custom_emoji.id === d.cv.emoji.custom_emoji.id,
|
||||
);
|
||||
if (original) {
|
||||
d.i.setState(() => {
|
||||
d.cv.emoji = structuredClone(original);
|
||||
d.cv.change = undefined;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async handleAddEmojiClick(form: EmojiForm, event: any) {
|
||||
event.preventDefault();
|
||||
form.setState(prevState => {
|
||||
const page =
|
||||
1 + Math.floor(prevState.customEmojis.length / form.itemsPerPage);
|
||||
const item: CustomEmojiViewForm = {
|
||||
id: 0,
|
||||
shortcode: "",
|
||||
alt_text: "",
|
||||
category: "",
|
||||
image_url: "",
|
||||
keywords: "",
|
||||
changed: false,
|
||||
page: page,
|
||||
loading: false,
|
||||
};
|
||||
|
||||
return {
|
||||
...prevState,
|
||||
customEmojis: [...prevState.customEmojis, item],
|
||||
page,
|
||||
};
|
||||
form.setState(prev => {
|
||||
prev.emojis.push({
|
||||
emoji: {
|
||||
custom_emoji: {
|
||||
id: -1,
|
||||
published: "",
|
||||
category: "",
|
||||
shortcode: "",
|
||||
image_url: "",
|
||||
alt_text: "",
|
||||
},
|
||||
keywords: [],
|
||||
},
|
||||
change: "create",
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -489,14 +575,15 @@ export class EmojiForm extends Component<EmojiFormProps, EmojiFormState> {
|
|||
file = event;
|
||||
}
|
||||
|
||||
form.setState(prevState => ({
|
||||
...prevState,
|
||||
customEmojis: prevState.customEmojis.map((cv, i) =>
|
||||
i === index ? { ...cv, loading: true } : cv,
|
||||
),
|
||||
}));
|
||||
const editable = form.state.emojis[index];
|
||||
form.setState(() => {
|
||||
editable.loading = true;
|
||||
});
|
||||
|
||||
HttpService.client.uploadImage({ image: file }).then(res => {
|
||||
form.setState(() => {
|
||||
editable.loading = false;
|
||||
});
|
||||
if (res.state === "success") {
|
||||
if (res.data.msg === "ok") {
|
||||
pictrsDeleteToast(file.name, res.data.delete_url as string);
|
||||
|
@ -517,10 +604,20 @@ export class EmojiForm extends Component<EmojiFormProps, EmojiFormState> {
|
|||
}
|
||||
|
||||
configurePicker(): any {
|
||||
const custom = this.state.emojiMartCustom;
|
||||
if (process.env["NODE_ENV"] === "development") {
|
||||
// Once an emoji-mart Picker is initialized with these options, other
|
||||
// instances also only show the custom emojis.
|
||||
console.assert(
|
||||
amAdmin(),
|
||||
"EmojiMart doesn't deal well with differently configured instances.",
|
||||
);
|
||||
}
|
||||
return {
|
||||
data: { categories: [], emojis: [], aliases: [] },
|
||||
maxFrequentRows: 0,
|
||||
dynamicWidth: true,
|
||||
custom,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import {
|
||||
commentsToFlatNodes,
|
||||
commentToPostSortType,
|
||||
editComment,
|
||||
editPost,
|
||||
editWith,
|
||||
|
@ -16,7 +17,6 @@ import {
|
|||
import {
|
||||
getQueryParams,
|
||||
getQueryString,
|
||||
getRandomFromList,
|
||||
resourcesSettled,
|
||||
} from "@utils/helpers";
|
||||
import { scrollMixin } from "../mixins/scroll-mixin";
|
||||
|
@ -67,9 +67,10 @@ import {
|
|||
RemovePost,
|
||||
SaveComment,
|
||||
SavePost,
|
||||
SortType,
|
||||
PostSortType,
|
||||
SuccessResponse,
|
||||
TransferCommunity,
|
||||
CommentSortType,
|
||||
} from "lemmy-js-client";
|
||||
import { fetchLimit, relTags } from "../../config";
|
||||
import {
|
||||
|
@ -107,6 +108,7 @@ import { RouteComponentProps } from "inferno-router/dist/Route";
|
|||
import { IRoutePropsWithFetch } from "../../routes";
|
||||
import PostHiddenSelect from "../common/post-hidden-select";
|
||||
import { isBrowser, snapToTop } from "@utils/browser";
|
||||
import { CommentSortSelect } from "../common/comment-sort-select";
|
||||
|
||||
interface HomeState {
|
||||
postsRes: RequestState<GetPostsResponse>;
|
||||
|
@ -122,7 +124,7 @@ interface HomeState {
|
|||
interface HomeProps {
|
||||
listingType?: ListingType;
|
||||
dataType: DataType;
|
||||
sort: SortType;
|
||||
sort: PostSortType;
|
||||
pageCursor?: PaginationCursor;
|
||||
showHidden?: StringBoolean;
|
||||
}
|
||||
|
@ -132,7 +134,7 @@ type HomeData = RouteDataResponse<{
|
|||
commentsRes: GetCommentsResponse;
|
||||
}>;
|
||||
|
||||
function getRss(listingType: ListingType, sort: SortType) {
|
||||
function getRss(listingType: ListingType, sort: PostSortType) {
|
||||
let rss: string | undefined = undefined;
|
||||
|
||||
const queryString = getQueryString({ sort });
|
||||
|
@ -177,13 +179,13 @@ function getListingTypeFromQuery(
|
|||
|
||||
function getSortTypeFromQuery(
|
||||
type: string | undefined,
|
||||
fallback: SortType,
|
||||
): SortType {
|
||||
return type ? (type as SortType) : fallback;
|
||||
fallback: PostSortType,
|
||||
): PostSortType {
|
||||
return type ? (type as PostSortType) : fallback;
|
||||
}
|
||||
|
||||
type Fallbacks = {
|
||||
sort: SortType;
|
||||
sort: PostSortType;
|
||||
listingType: ListingType;
|
||||
};
|
||||
|
||||
|
@ -204,7 +206,8 @@ export function getHomeQueryParams(
|
|||
},
|
||||
source,
|
||||
{
|
||||
sort: local_user?.default_sort_type ?? local_site.default_sort_type,
|
||||
sort:
|
||||
local_user?.default_post_sort_type ?? local_site.default_post_sort_type,
|
||||
listingType:
|
||||
local_user?.default_listing_type ??
|
||||
local_site.default_post_listing_type,
|
||||
|
@ -264,6 +267,7 @@ export class Home extends Component<HomeRouteProps, HomeState> {
|
|||
super(props, context);
|
||||
|
||||
this.handleSortChange = this.handleSortChange.bind(this);
|
||||
this.handleCommentSortChange = this.handleCommentSortChange.bind(this);
|
||||
this.handleListingTypeChange = this.handleListingTypeChange.bind(this);
|
||||
this.handleDataTypeChange = this.handleDataTypeChange.bind(this);
|
||||
this.handleShowHiddenChange = this.handleShowHiddenChange.bind(this);
|
||||
|
@ -311,9 +315,7 @@ export class Home extends Component<HomeRouteProps, HomeState> {
|
|||
};
|
||||
}
|
||||
|
||||
this.state.tagline = getRandomFromList(
|
||||
this.state?.siteRes?.taglines ?? [],
|
||||
)?.content;
|
||||
this.state.tagline = this.state?.siteRes?.tagline?.content;
|
||||
}
|
||||
|
||||
async componentWillMount() {
|
||||
|
@ -726,7 +728,14 @@ export class Home extends Component<HomeRouteProps, HomeState> {
|
|||
/>
|
||||
</div>
|
||||
<div className="col-auto">
|
||||
<SortSelect sort={sort} onChange={this.handleSortChange} />
|
||||
{this.props.dataType === DataType.Post ? (
|
||||
<SortSelect sort={sort} onChange={this.handleSortChange} />
|
||||
) : (
|
||||
<CommentSortSelect
|
||||
sort={postToCommentSortType(sort)}
|
||||
onChange={this.handleCommentSortChange}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="col-auto ps-0">
|
||||
{getRss(
|
||||
|
@ -799,10 +808,14 @@ export class Home extends Component<HomeRouteProps, HomeState> {
|
|||
this.updateUrl({ pageCursor: nextPage });
|
||||
}
|
||||
|
||||
handleSortChange(val: SortType) {
|
||||
handleSortChange(val: PostSortType) {
|
||||
this.updateUrl({ sort: val, pageCursor: undefined });
|
||||
}
|
||||
|
||||
handleCommentSortChange(val: CommentSortType) {
|
||||
this.updateUrl({ sort: commentToPostSortType(val), pageCursor: undefined });
|
||||
}
|
||||
|
||||
handleListingTypeChange(val: ListingType) {
|
||||
this.updateUrl({ listingType: val, pageCursor: undefined });
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@ import { capitalizeFirstLetter, validInstanceTLD } from "@utils/helpers";
|
|||
import {
|
||||
Component,
|
||||
InfernoKeyboardEvent,
|
||||
InfernoMouseEvent,
|
||||
InfernoNode,
|
||||
linkEvent,
|
||||
} from "inferno";
|
||||
|
@ -877,42 +876,6 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
|
|||
this.setState(s => ((s.siteForm.legal_information = val), s));
|
||||
}
|
||||
|
||||
handleTaglineChange(i: SiteForm, index: number, val: string) {
|
||||
const taglines = i.state.siteForm.taglines;
|
||||
if (taglines) {
|
||||
taglines[index] = val;
|
||||
i.setState(i.state);
|
||||
}
|
||||
}
|
||||
|
||||
handleDeleteTaglineClick(
|
||||
i: SiteForm,
|
||||
index: number,
|
||||
event: InfernoMouseEvent<HTMLButtonElement>,
|
||||
) {
|
||||
event.preventDefault();
|
||||
const taglines = i.state.siteForm.taglines;
|
||||
if (taglines) {
|
||||
taglines.splice(index, 1);
|
||||
i.state.siteForm.taglines = undefined;
|
||||
i.setState(i.state);
|
||||
i.state.siteForm.taglines = taglines;
|
||||
i.setState(i.state);
|
||||
}
|
||||
}
|
||||
|
||||
handleAddTaglineClick(
|
||||
i: SiteForm,
|
||||
event: InfernoMouseEvent<HTMLButtonElement>,
|
||||
) {
|
||||
event.preventDefault();
|
||||
if (!i.state.siteForm.taglines) {
|
||||
i.state.siteForm.taglines = [];
|
||||
}
|
||||
i.state.siteForm.taglines.push("");
|
||||
i.setState(i.state);
|
||||
}
|
||||
|
||||
handleSiteApplicationQuestionChange(val: string) {
|
||||
this.setState(s => ((s.siteForm.application_question = val), s));
|
||||
}
|
||||
|
|
|
@ -1,49 +1,84 @@
|
|||
import { capitalizeFirstLetter } from "@utils/helpers";
|
||||
import { Component, InfernoMouseEvent, linkEvent } from "inferno";
|
||||
import { EditSite, Tagline } from "lemmy-js-client";
|
||||
import { I18NextService } from "../../services";
|
||||
import { Tagline } from "lemmy-js-client";
|
||||
import { HttpService, I18NextService } from "../../services";
|
||||
import { Icon, Spinner } from "../common/icon";
|
||||
import { MarkdownTextArea } from "../common/markdown-textarea";
|
||||
import { tippyMixin } from "../mixins/tippy-mixin";
|
||||
import { Paginator } from "../common/paginator";
|
||||
import classNames from "classnames";
|
||||
import { isBrowser } from "@utils/browser";
|
||||
import { Prompt } from "inferno-router";
|
||||
|
||||
interface TaglineFormProps {
|
||||
taglines: Array<Tagline>;
|
||||
onSaveSite(form: EditSite): void;
|
||||
loading: boolean;
|
||||
interface EditableTagline {
|
||||
change?: "update" | "delete" | "create";
|
||||
editMode?: boolean;
|
||||
tagline: Tagline;
|
||||
}
|
||||
|
||||
function markForUpdate(editable: EditableTagline) {
|
||||
if (editable.change !== "create") {
|
||||
editable.change = "update";
|
||||
}
|
||||
}
|
||||
|
||||
interface TaglineFormState {
|
||||
taglines: Array<string>;
|
||||
editingRow?: number;
|
||||
taglines: Array<EditableTagline>;
|
||||
page: number;
|
||||
loading: boolean;
|
||||
}
|
||||
|
||||
@tippyMixin
|
||||
export class TaglineForm extends Component<TaglineFormProps, TaglineFormState> {
|
||||
export class TaglineForm extends Component<
|
||||
Record<never, never>,
|
||||
TaglineFormState
|
||||
> {
|
||||
state: TaglineFormState = {
|
||||
editingRow: undefined,
|
||||
taglines: this.props.taglines.map(x => x.content),
|
||||
taglines: [],
|
||||
page: 1,
|
||||
loading: false,
|
||||
};
|
||||
constructor(props: any, context: any) {
|
||||
super(props, context);
|
||||
this.handlePageChange = this.handlePageChange.bind(this);
|
||||
}
|
||||
|
||||
componentWillMount(): void {
|
||||
if (isBrowser()) {
|
||||
this.handlePageChange(1);
|
||||
}
|
||||
}
|
||||
|
||||
hasPendingChanges(): boolean {
|
||||
return this.state.taglines.some(x => x.change);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="tagline-form col-12">
|
||||
<Prompt
|
||||
message={I18NextService.i18n.t("block_leaving")}
|
||||
when={this.hasPendingChanges()}
|
||||
/>
|
||||
<h1 className="h4 mb-4">{I18NextService.i18n.t("taglines")}</h1>
|
||||
<div className="table-responsive col-12">
|
||||
<table id="taglines_table" className="table table-sm table-hover">
|
||||
<table
|
||||
id="taglines_table"
|
||||
className="table table-sm table-hover align-middle"
|
||||
>
|
||||
<thead className="pointer">
|
||||
<th></th>
|
||||
<th style="width:60px"></th>
|
||||
<th style="width:121px"></th>
|
||||
</thead>
|
||||
<tbody>
|
||||
{this.state.taglines.map((cv, index) => (
|
||||
<tr key={index}>
|
||||
<td>
|
||||
{this.state.editingRow === index && (
|
||||
{cv.editMode ? (
|
||||
<MarkdownTextArea
|
||||
initialContent={cv}
|
||||
initialContent={cv.tagline.content}
|
||||
focus={true}
|
||||
onContentChange={s =>
|
||||
this.handleTaglineChange(this, index, s)
|
||||
}
|
||||
|
@ -51,8 +86,32 @@ export class TaglineForm extends Component<TaglineFormProps, TaglineFormState> {
|
|||
allLanguages={[]}
|
||||
siteLanguages={[]}
|
||||
/>
|
||||
) : (
|
||||
<div>{cv.tagline.content}</div>
|
||||
)}
|
||||
</td>
|
||||
<td
|
||||
className={classNames("text-center", {
|
||||
"border-info": cv.change === "update",
|
||||
"border-danger": cv.change === "delete",
|
||||
"border-warning": cv.change === "create",
|
||||
})}
|
||||
>
|
||||
{cv.change === "update" && (
|
||||
<span>
|
||||
<Icon icon="transfer" />
|
||||
</span>
|
||||
)}
|
||||
{cv.change === "delete" && (
|
||||
<span>
|
||||
<Icon icon="trash" />
|
||||
</span>
|
||||
)}
|
||||
{cv.change === "create" && (
|
||||
<span>
|
||||
<Icon icon="add" inline />
|
||||
</span>
|
||||
)}
|
||||
{this.state.editingRow !== index && <div>{cv}</div>}
|
||||
</td>
|
||||
<td className="text-right">
|
||||
<button
|
||||
|
@ -99,65 +158,153 @@ export class TaglineForm extends Component<TaglineFormProps, TaglineFormState> {
|
|||
<button
|
||||
onClick={linkEvent(this, this.handleSaveClick)}
|
||||
className="btn btn-secondary me-2"
|
||||
disabled={this.props.loading}
|
||||
disabled={this.state.loading || !this.hasPendingChanges()}
|
||||
>
|
||||
{this.props.loading ? (
|
||||
{this.state.loading ? (
|
||||
<Spinner />
|
||||
) : (
|
||||
capitalizeFirstLetter(I18NextService.i18n.t("save"))
|
||||
)}
|
||||
</button>
|
||||
{this.hasPendingChanges() && (
|
||||
<button
|
||||
onClick={linkEvent(this, this.handleCancelClick)}
|
||||
className="btn btn-secondary me-2"
|
||||
>
|
||||
{I18NextService.i18n.t("cancel")}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<Paginator
|
||||
page={this.state.page}
|
||||
onChange={this.handlePageChange}
|
||||
nextDisabled={false}
|
||||
disabled={this.hasPendingChanges()}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
handleTaglineChange(i: TaglineForm, index: number, val: string) {
|
||||
if (i.state.taglines) {
|
||||
i.setState(prev => ({
|
||||
...prev,
|
||||
taglines: prev.taglines.map((tl, i) => (i === index ? val : tl)),
|
||||
}));
|
||||
}
|
||||
const editable = i.state.taglines[index];
|
||||
i.setState(() => {
|
||||
markForUpdate(editable);
|
||||
const tagline: Tagline = editable.tagline;
|
||||
tagline.content = val;
|
||||
});
|
||||
}
|
||||
|
||||
handleDeleteTaglineClick(d: { i: TaglineForm; index: number }, event: any) {
|
||||
async handleDeleteTaglineClick(
|
||||
d: { i: TaglineForm; index: number },
|
||||
event: any,
|
||||
) {
|
||||
event.preventDefault();
|
||||
d.i.setState(prev => ({
|
||||
...prev,
|
||||
taglines: prev.taglines.filter((_, i) => i !== d.index),
|
||||
editingRow: undefined,
|
||||
}));
|
||||
const editable = d.i.state.taglines[d.index];
|
||||
if (editable.change === "create") {
|
||||
// This drops the entry immediately, other deletes have to be saved.
|
||||
d.i.setState(prev => {
|
||||
return { taglines: prev.taglines.filter(x => x !== editable) };
|
||||
});
|
||||
} else {
|
||||
d.i.setState(() => {
|
||||
editable.change = "delete";
|
||||
editable.editMode = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
handleEditTaglineClick(d: { i: TaglineForm; index: number }, event: any) {
|
||||
event.preventDefault();
|
||||
if (d.i.state.editingRow === d.index) {
|
||||
d.i.setState({ editingRow: undefined });
|
||||
} else {
|
||||
d.i.setState({ editingRow: d.index });
|
||||
}
|
||||
}
|
||||
|
||||
async handleSaveClick(i: TaglineForm) {
|
||||
i.props.onSaveSite({
|
||||
taglines: i.state.taglines,
|
||||
const editable = d.i.state.taglines[d.index];
|
||||
d.i.setState(prev => {
|
||||
prev.taglines
|
||||
.filter(x => x !== editable)
|
||||
.forEach(x => {
|
||||
x.editMode = false;
|
||||
});
|
||||
editable.editMode = !editable.editMode;
|
||||
});
|
||||
}
|
||||
|
||||
handleAddTaglineClick(
|
||||
async handleSaveClick(i: TaglineForm) {
|
||||
const promises: Promise<any>[] = [];
|
||||
for (const editable of i.state.taglines) {
|
||||
if (editable.change === "update") {
|
||||
promises.push(
|
||||
HttpService.client.editTagline(editable.tagline).then(res => {
|
||||
if (res.state === "success") {
|
||||
i.setState(() => {
|
||||
editable.change = undefined;
|
||||
editable.tagline = res.data.tagline;
|
||||
});
|
||||
}
|
||||
}),
|
||||
);
|
||||
} else if (editable.change === "delete") {
|
||||
promises.push(
|
||||
HttpService.client.deleteTagline(editable.tagline).then(res => {
|
||||
if (res.state === "success") {
|
||||
i.setState(() => {
|
||||
editable.change = undefined;
|
||||
return {
|
||||
taglines: this.state.taglines.filter(x => x !== editable),
|
||||
};
|
||||
});
|
||||
}
|
||||
}),
|
||||
);
|
||||
} else if (editable.change === "create") {
|
||||
promises.push(
|
||||
HttpService.client.createTagline(editable.tagline).then(res => {
|
||||
if (res.state === "success") {
|
||||
i.setState(() => {
|
||||
editable.change = undefined;
|
||||
editable.tagline = res.data.tagline;
|
||||
});
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
await Promise.all(promises);
|
||||
}
|
||||
|
||||
async handleCancelClick(i: TaglineForm) {
|
||||
i.handlePageChange(i.state.page);
|
||||
}
|
||||
|
||||
async handleAddTaglineClick(
|
||||
i: TaglineForm,
|
||||
event: InfernoMouseEvent<HTMLButtonElement>,
|
||||
) {
|
||||
event.preventDefault();
|
||||
const newTaglines = [...i.state.taglines];
|
||||
newTaglines.push("");
|
||||
|
||||
i.setState({
|
||||
taglines: newTaglines,
|
||||
editingRow: newTaglines.length - 1,
|
||||
i.setState(prev => {
|
||||
prev.taglines.forEach(x => {
|
||||
x.editMode = false;
|
||||
});
|
||||
prev.taglines.push({
|
||||
tagline: { id: -1, content: "", published: "" },
|
||||
change: "create",
|
||||
editMode: true,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async handlePageChange(val: number) {
|
||||
this.setState({ loading: true });
|
||||
const taglineRes = await HttpService.client.listTaglines({ page: val });
|
||||
if (taglineRes.state === "success") {
|
||||
this.setState({
|
||||
page: val,
|
||||
loading: false,
|
||||
taglines: taglineRes.data.taglines.map(t => ({ tagline: t })),
|
||||
});
|
||||
} else {
|
||||
this.setState({ loading: false });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,7 +37,7 @@ import {
|
|||
RemovePost,
|
||||
SaveComment,
|
||||
SavePost,
|
||||
SortType,
|
||||
PostSortType,
|
||||
TransferCommunity,
|
||||
} from "lemmy-js-client";
|
||||
import { CommentViewType, PersonDetailsView } from "../../interfaces";
|
||||
|
@ -53,7 +53,7 @@ interface PersonDetailsProps {
|
|||
siteLanguages: number[];
|
||||
page: number;
|
||||
limit: number;
|
||||
sort: SortType;
|
||||
sort: PostSortType;
|
||||
enableDownvotes: boolean;
|
||||
voteDisplayMode: LocalUserVoteDisplayMode;
|
||||
enableNsfw: boolean;
|
||||
|
|
|
@ -70,7 +70,7 @@ import {
|
|||
RemovePost,
|
||||
SaveComment,
|
||||
SavePost,
|
||||
SortType,
|
||||
PostSortType,
|
||||
SuccessResponse,
|
||||
TransferCommunity,
|
||||
RegistrationApplicationResponse,
|
||||
|
@ -127,7 +127,7 @@ interface ProfileState {
|
|||
|
||||
interface ProfileProps {
|
||||
view: PersonDetailsView;
|
||||
sort: SortType;
|
||||
sort: PostSortType;
|
||||
page: number;
|
||||
}
|
||||
|
||||
|
@ -142,8 +142,8 @@ export function getProfileQueryParams(source?: string): ProfileProps {
|
|||
);
|
||||
}
|
||||
|
||||
function getSortTypeFromQuery(sort?: string): SortType {
|
||||
return sort ? (sort as SortType) : "New";
|
||||
function getSortTypeFromQuery(sort?: string): PostSortType {
|
||||
return sort ? (sort as PostSortType) : "New";
|
||||
}
|
||||
|
||||
function getViewFromProps(view?: string): PersonDetailsView {
|
||||
|
@ -956,7 +956,7 @@ export class Profile extends Component<ProfileRouteProps, ProfileState> {
|
|||
this.updateUrl({ page });
|
||||
}
|
||||
|
||||
handleSortChange(sort: SortType) {
|
||||
handleSortChange(sort: PostSortType) {
|
||||
this.updateUrl({ sort, page: 1 });
|
||||
}
|
||||
|
||||
|
|
|
@ -22,6 +22,7 @@ import {
|
|||
BlockCommunityResponse,
|
||||
BlockInstanceResponse,
|
||||
BlockPersonResponse,
|
||||
CommentSortType,
|
||||
Community,
|
||||
GenerateTotpSecretResponse,
|
||||
GetFederatedInstancesResponse,
|
||||
|
@ -31,7 +32,7 @@ import {
|
|||
ListingType,
|
||||
LoginResponse,
|
||||
Person,
|
||||
SortType,
|
||||
PostSortType,
|
||||
SuccessResponse,
|
||||
UpdateTotpResponse,
|
||||
} from "lemmy-js-client";
|
||||
|
@ -76,6 +77,7 @@ import { getHttpBaseInternal } from "../../utils/env";
|
|||
import { IRoutePropsWithFetch } from "../../routes";
|
||||
import { RouteComponentProps } from "inferno-router/dist/Route";
|
||||
import { simpleScrollMixin } from "../mixins/scroll-mixin";
|
||||
import { CommentSortSelect } from "../common/comment-sort-select";
|
||||
|
||||
type SettingsData = RouteDataResponse<{
|
||||
instancesRes: GetFederatedInstancesResponse;
|
||||
|
@ -94,7 +96,8 @@ interface SettingsState {
|
|||
blur_nsfw?: boolean;
|
||||
auto_expand?: boolean;
|
||||
theme?: string;
|
||||
default_sort_type?: SortType;
|
||||
default_post_sort_type?: PostSortType;
|
||||
default_comment_sort_type?: CommentSortType;
|
||||
default_listing_type?: ListingType;
|
||||
interface_language?: string;
|
||||
avatar?: string;
|
||||
|
@ -248,7 +251,9 @@ export class Settings extends Component<SettingsRouteProps, SettingsState> {
|
|||
constructor(props: any, context: any) {
|
||||
super(props, context);
|
||||
|
||||
this.handleSortTypeChange = this.handleSortTypeChange.bind(this);
|
||||
this.handlePostSortTypeChange = this.handlePostSortTypeChange.bind(this);
|
||||
this.handleCommentSortTypeChange =
|
||||
this.handleCommentSortTypeChange.bind(this);
|
||||
this.handleListingTypeChange = this.handleListingTypeChange.bind(this);
|
||||
this.handleBioChange = this.handleBioChange.bind(this);
|
||||
this.handleDiscussionLanguageChange =
|
||||
|
@ -276,9 +281,9 @@ export class Settings extends Component<SettingsRouteProps, SettingsState> {
|
|||
local_user: {
|
||||
show_nsfw,
|
||||
blur_nsfw,
|
||||
auto_expand,
|
||||
theme,
|
||||
default_sort_type,
|
||||
default_post_sort_type,
|
||||
default_comment_sort_type,
|
||||
default_listing_type,
|
||||
interface_language,
|
||||
show_avatars,
|
||||
|
@ -313,9 +318,9 @@ export class Settings extends Component<SettingsRouteProps, SettingsState> {
|
|||
...this.state.saveUserSettingsForm,
|
||||
show_nsfw,
|
||||
blur_nsfw,
|
||||
auto_expand,
|
||||
theme: theme ?? "browser",
|
||||
default_sort_type,
|
||||
default_post_sort_type,
|
||||
default_comment_sort_type,
|
||||
default_listing_type,
|
||||
interface_language,
|
||||
discussion_languages: mui.discussion_languages,
|
||||
|
@ -905,14 +910,29 @@ export class Settings extends Component<SettingsRouteProps, SettingsState> {
|
|||
</form>
|
||||
<form className="mb-3 row">
|
||||
<label className="col-sm-3 col-form-label">
|
||||
{I18NextService.i18n.t("sort_type")}
|
||||
{I18NextService.i18n.t("post_sort_type")}
|
||||
</label>
|
||||
<div className="col-sm-9">
|
||||
<SortSelect
|
||||
sort={
|
||||
this.state.saveUserSettingsForm.default_sort_type ?? "Active"
|
||||
this.state.saveUserSettingsForm.default_post_sort_type ??
|
||||
"Active"
|
||||
}
|
||||
onChange={this.handleSortTypeChange}
|
||||
onChange={this.handlePostSortTypeChange}
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
<form className="mb-3 row">
|
||||
<label className="col-sm-3 col-form-label">
|
||||
{I18NextService.i18n.t("comment_sort_type")}
|
||||
</label>
|
||||
<div className="col-sm-9">
|
||||
<CommentSortSelect
|
||||
sort={
|
||||
this.state.saveUserSettingsForm.default_comment_sort_type ??
|
||||
"Hot"
|
||||
}
|
||||
onChange={this.handleCommentSortTypeChange}
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
|
@ -1580,8 +1600,16 @@ export class Settings extends Component<SettingsRouteProps, SettingsState> {
|
|||
);
|
||||
}
|
||||
|
||||
handleSortTypeChange(val: SortType) {
|
||||
this.setState(s => ((s.saveUserSettingsForm.default_sort_type = val), s));
|
||||
handlePostSortTypeChange(val: PostSortType) {
|
||||
this.setState(
|
||||
s => ((s.saveUserSettingsForm.default_post_sort_type = val), s),
|
||||
);
|
||||
}
|
||||
|
||||
handleCommentSortTypeChange(val: CommentSortType) {
|
||||
this.setState(
|
||||
s => ((s.saveUserSettingsForm.default_comment_sort_type = val), s),
|
||||
);
|
||||
}
|
||||
|
||||
handleListingTypeChange(val: ListingType) {
|
||||
|
@ -1743,9 +1771,9 @@ export class Settings extends Component<SettingsRouteProps, SettingsState> {
|
|||
local_user: {
|
||||
show_nsfw,
|
||||
blur_nsfw,
|
||||
auto_expand,
|
||||
theme,
|
||||
default_sort_type,
|
||||
default_post_sort_type,
|
||||
default_comment_sort_type,
|
||||
default_listing_type,
|
||||
interface_language,
|
||||
show_avatars,
|
||||
|
@ -1782,11 +1810,11 @@ export class Settings extends Component<SettingsRouteProps, SettingsState> {
|
|||
display_name,
|
||||
bio,
|
||||
matrix_user_id,
|
||||
auto_expand,
|
||||
blur_nsfw,
|
||||
bot_account,
|
||||
default_listing_type,
|
||||
default_sort_type,
|
||||
default_post_sort_type,
|
||||
default_comment_sort_type,
|
||||
discussion_languages: siteRes.data.my_user?.discussion_languages,
|
||||
email,
|
||||
interface_language,
|
||||
|
|
|
@ -148,10 +148,10 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
|
|||
UserService.Instance.myUserInfo &&
|
||||
!this.isoData.showAdultConsentModal
|
||||
) {
|
||||
const { auto_expand, blur_nsfw } =
|
||||
UserService.Instance.myUserInfo.local_user_view.local_user;
|
||||
const blur_nsfw =
|
||||
UserService.Instance.myUserInfo.local_user_view.local_user.blur_nsfw;
|
||||
this.setState({
|
||||
imageExpanded: auto_expand && !(blur_nsfw && this.postView.post.nsfw),
|
||||
imageExpanded: !(blur_nsfw && this.postView.post.nsfw),
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -124,11 +124,12 @@ interface PostState {
|
|||
lastCreatedCommentId?: CommentId;
|
||||
}
|
||||
|
||||
const defaultCommentSort: CommentSortType = "Hot";
|
||||
|
||||
function getCommentSortTypeFromQuery(source?: string): CommentSortType {
|
||||
function getCommentSortTypeFromQuery(
|
||||
source: string | undefined,
|
||||
fallback: CommentSortType,
|
||||
): CommentSortType {
|
||||
if (!source) {
|
||||
return defaultCommentSort;
|
||||
return fallback;
|
||||
}
|
||||
switch (source) {
|
||||
case "Hot":
|
||||
|
@ -138,14 +139,21 @@ function getCommentSortTypeFromQuery(source?: string): CommentSortType {
|
|||
case "Controversial":
|
||||
return source;
|
||||
default:
|
||||
return defaultCommentSort;
|
||||
return fallback;
|
||||
}
|
||||
}
|
||||
|
||||
function getQueryStringFromCommentSortType(
|
||||
sort: CommentSortType,
|
||||
siteRes: GetSiteResponse,
|
||||
): undefined | string {
|
||||
if (sort === defaultCommentSort) {
|
||||
const myUserInfo = siteRes.my_user ?? UserService.Instance.myUserInfo;
|
||||
const local_user = myUserInfo?.local_user_view.local_user;
|
||||
const local_site = siteRes.site_view.local_site;
|
||||
const defaultSort =
|
||||
local_user?.default_comment_sort_type ??
|
||||
local_site.default_comment_sort_type;
|
||||
if (sort === defaultSort) {
|
||||
return undefined;
|
||||
}
|
||||
return sort;
|
||||
|
@ -185,14 +193,31 @@ interface PostProps {
|
|||
view: CommentViewType;
|
||||
scrollToComments: boolean;
|
||||
}
|
||||
export function getPostQueryParams(source: string | undefined): PostProps {
|
||||
return getQueryParams<PostProps>(
|
||||
|
||||
type Fallbacks = {
|
||||
sort: CommentSortType;
|
||||
};
|
||||
|
||||
export function getPostQueryParams(
|
||||
source: string | undefined,
|
||||
siteRes: GetSiteResponse,
|
||||
): PostProps {
|
||||
const myUserInfo = siteRes.my_user ?? UserService.Instance.myUserInfo;
|
||||
const local_user = myUserInfo?.local_user_view.local_user;
|
||||
const local_site = siteRes.site_view.local_site;
|
||||
|
||||
return getQueryParams<PostProps, Fallbacks>(
|
||||
{
|
||||
scrollToComments: (s?: string) => !!s,
|
||||
sort: getCommentSortTypeFromQuery,
|
||||
view: getCommentViewTypeFromQuery,
|
||||
},
|
||||
source,
|
||||
{
|
||||
sort:
|
||||
local_user?.default_comment_sort_type ??
|
||||
local_site.default_comment_sort_type,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -325,7 +350,7 @@ export class Post extends Component<PostRouteProps, PostState> {
|
|||
};
|
||||
|
||||
const query: QueryParams<PostProps> = {
|
||||
sort: getQueryStringFromCommentSortType(sort),
|
||||
sort: getQueryStringFromCommentSortType(sort, this.state.siteRes),
|
||||
view: getQueryStringFromCommentView(view),
|
||||
};
|
||||
|
||||
|
|
|
@ -46,7 +46,7 @@ import {
|
|||
Search as SearchForm,
|
||||
SearchResponse,
|
||||
SearchType,
|
||||
SortType,
|
||||
PostSortType,
|
||||
} from "lemmy-js-client";
|
||||
import { fetchLimit } from "../config";
|
||||
import { CommentViewType, InitialFetchRequest } from "../interfaces";
|
||||
|
@ -76,9 +76,9 @@ import { isBrowser } from "@utils/browser";
|
|||
interface SearchProps {
|
||||
q?: string;
|
||||
type: SearchType;
|
||||
sort: SortType;
|
||||
sort: PostSortType;
|
||||
listingType: ListingType;
|
||||
postTitleOnly?: boolean;
|
||||
titleOnly?: boolean;
|
||||
communityId?: number;
|
||||
creatorId?: number;
|
||||
page: number;
|
||||
|
@ -124,7 +124,7 @@ export function getSearchQueryParams(source?: string): SearchProps {
|
|||
type: getSearchTypeFromQuery,
|
||||
sort: getSortTypeFromQuery,
|
||||
listingType: getListingTypeFromQuery,
|
||||
postTitleOnly: getBoolFromString,
|
||||
titleOnly: getBoolFromString,
|
||||
communityId: getIdFromString,
|
||||
creatorId: getIdFromString,
|
||||
page: getPageFromString,
|
||||
|
@ -139,8 +139,8 @@ function getSearchTypeFromQuery(type_?: string): SearchType {
|
|||
return type_ ? (type_ as SearchType) : defaultSearchType;
|
||||
}
|
||||
|
||||
function getSortTypeFromQuery(sort?: string): SortType {
|
||||
return sort ? (sort as SortType) : defaultSortType;
|
||||
function getSortTypeFromQuery(sort?: string): PostSortType {
|
||||
return sort ? (sort as PostSortType) : defaultSortType;
|
||||
}
|
||||
|
||||
function getListingTypeFromQuery(listingType?: string): ListingType {
|
||||
|
@ -286,7 +286,7 @@ export class Search extends Component<SearchRouteProps, SearchState> {
|
|||
this.handleCommunityFilterChange =
|
||||
this.handleCommunityFilterChange.bind(this);
|
||||
this.handleCreatorFilterChange = this.handleCreatorFilterChange.bind(this);
|
||||
this.handlePostTitleChange = this.handlePostTitleChange.bind(this);
|
||||
this.handleTitleOnlyChange = this.handleTitleOnlyChange.bind(this);
|
||||
|
||||
// Only fetch the data if coming from another route
|
||||
if (FirstLoadService.isFirstLoad) {
|
||||
|
@ -473,7 +473,7 @@ export class Search extends Component<SearchRouteProps, SearchState> {
|
|||
type: searchType,
|
||||
sort,
|
||||
listingType: listing_type,
|
||||
postTitleOnly: post_title_only,
|
||||
titleOnly: title_only,
|
||||
communityId: community_id,
|
||||
creatorId: creator_id,
|
||||
page,
|
||||
|
@ -519,7 +519,7 @@ export class Search extends Component<SearchRouteProps, SearchState> {
|
|||
type_: searchType,
|
||||
sort,
|
||||
listing_type,
|
||||
post_title_only,
|
||||
title_only,
|
||||
page,
|
||||
limit: fetchLimit,
|
||||
};
|
||||
|
@ -595,7 +595,6 @@ export class Search extends Component<SearchRouteProps, SearchState> {
|
|||
case "Comments":
|
||||
return this.comments;
|
||||
case "Posts":
|
||||
case "Url":
|
||||
return this.posts;
|
||||
case "Communities":
|
||||
return this.communities;
|
||||
|
@ -641,7 +640,7 @@ export class Search extends Component<SearchRouteProps, SearchState> {
|
|||
}
|
||||
|
||||
get selects() {
|
||||
const { type, listingType, postTitleOnly, sort, communityId, creatorId } =
|
||||
const { type, listingType, titleOnly, sort, communityId, creatorId } =
|
||||
this.props;
|
||||
const {
|
||||
communitySearchOptions,
|
||||
|
@ -684,15 +683,12 @@ export class Search extends Component<SearchRouteProps, SearchState> {
|
|||
<div className="col">
|
||||
<input
|
||||
className="btn-check"
|
||||
id="post-title-only"
|
||||
id="title-only"
|
||||
type="checkbox"
|
||||
checked={postTitleOnly}
|
||||
onChange={this.handlePostTitleChange}
|
||||
checked={titleOnly}
|
||||
onChange={this.handleTitleOnlyChange}
|
||||
/>
|
||||
<label
|
||||
className="btn btn-outline-secondary"
|
||||
htmlFor="post-title-only"
|
||||
>
|
||||
<label className="btn btn-outline-secondary" htmlFor="title-only">
|
||||
{I18NextService.i18n.t("post_title_only")}
|
||||
</label>
|
||||
</div>
|
||||
|
@ -1074,7 +1070,7 @@ export class Search extends Component<SearchRouteProps, SearchState> {
|
|||
type,
|
||||
sort,
|
||||
listingType,
|
||||
postTitleOnly,
|
||||
titleOnly,
|
||||
page,
|
||||
} = props;
|
||||
|
||||
|
@ -1087,7 +1083,7 @@ export class Search extends Component<SearchRouteProps, SearchState> {
|
|||
type_: type,
|
||||
sort,
|
||||
listing_type: listingType,
|
||||
post_title_only: postTitleOnly,
|
||||
title_only: titleOnly,
|
||||
page,
|
||||
limit: fetchLimit,
|
||||
});
|
||||
|
@ -1152,13 +1148,13 @@ export class Search extends Component<SearchRouteProps, SearchState> {
|
|||
return this.searchInput.current?.value ?? this.props.q;
|
||||
}
|
||||
|
||||
handleSortChange(sort: SortType) {
|
||||
handleSortChange(sort: PostSortType) {
|
||||
this.updateUrl({ sort, page: 1, q: this.getQ() });
|
||||
}
|
||||
|
||||
handlePostTitleChange(event: any) {
|
||||
const postTitleOnly = event.target.checked;
|
||||
this.updateUrl({ postTitleOnly, q: this.getQ() });
|
||||
handleTitleOnlyChange(event: any) {
|
||||
const titleOnly = event.target.checked;
|
||||
this.updateUrl({ titleOnly, q: this.getQ() });
|
||||
}
|
||||
|
||||
handleTypeChange(i: Search, event: any) {
|
||||
|
@ -1213,7 +1209,7 @@ export class Search extends Component<SearchRouteProps, SearchState> {
|
|||
q,
|
||||
type,
|
||||
listingType,
|
||||
postTitleOnly,
|
||||
titleOnly,
|
||||
sort,
|
||||
communityId,
|
||||
creatorId,
|
||||
|
@ -1227,7 +1223,7 @@ export class Search extends Component<SearchRouteProps, SearchState> {
|
|||
q,
|
||||
type: type,
|
||||
listingType: listingType,
|
||||
postTitleOnly: postTitleOnly?.toString(),
|
||||
titleOnly: titleOnly?.toString(),
|
||||
communityId: communityId?.toString(),
|
||||
creatorId: creatorId?.toString(),
|
||||
page: page?.toString(),
|
||||
|
|
|
@ -17,6 +17,8 @@ import markdown_it_highlightjs from "markdown-it-highlightjs/core";
|
|||
import { Renderer, Token } from "markdown-it";
|
||||
import { instanceLinkRegex, relTags } from "./config";
|
||||
import { lazyHighlightjs } from "./lazy-highlightjs";
|
||||
import { HttpService } from "./services";
|
||||
import { WrappedLemmyHttp } from "./services/HttpService";
|
||||
|
||||
let Tribute: any;
|
||||
|
||||
|
@ -32,7 +34,7 @@ export const mdLimited: MarkdownIt = new MarkdownIt("zero").enable([
|
|||
"strikethrough",
|
||||
]);
|
||||
|
||||
export const customEmojis: EmojiMartCategory[] = [];
|
||||
let customEmojis: EmojiMartCategory[] = [];
|
||||
|
||||
export let customEmojisLookup: Map<string, CustomEmojiView> = new Map<
|
||||
string,
|
||||
|
@ -204,14 +206,17 @@ export function setupMarkdown() {
|
|||
) {
|
||||
//Provide custom renderer for our emojis to allow us to add a css class and force size dimensions on them.
|
||||
const item = tokens[idx] as any;
|
||||
let title = item.attrs.length >= 3 ? item.attrs[2][1] : "";
|
||||
const url = item.attrs.length > 0 ? item.attrs[0][1] : "";
|
||||
const altText = item.attrs.length > 1 ? item.attrs[1][1] : "";
|
||||
const title = item.attrs.length > 2 ? item.attrs[2][1] : "";
|
||||
const splitTitle = title.split(/ (.*)/, 2);
|
||||
const isEmoji = splitTitle[0] === "emoji";
|
||||
let shortcode: string | undefined;
|
||||
if (isEmoji) {
|
||||
title = splitTitle[1];
|
||||
shortcode = splitTitle[1];
|
||||
}
|
||||
const customEmoji = customEmojisLookup.get(title);
|
||||
const isLocalEmoji = customEmoji !== undefined;
|
||||
// customEmojisLookup is empty in SSR, CSR rerenders markdown anyway
|
||||
const isLocalEmoji = shortcode && customEmojisLookup.has(shortcode);
|
||||
if (!isLocalEmoji) {
|
||||
const imgElement =
|
||||
defaultImageRenderer?.(tokens, idx, options, env, self) ?? "";
|
||||
|
@ -222,10 +227,8 @@ export function setupMarkdown() {
|
|||
} else return "";
|
||||
}
|
||||
return `<img class="icon icon-emoji" src="${
|
||||
customEmoji!.custom_emoji.image_url
|
||||
}" title="${customEmoji!.custom_emoji.shortcode}" alt="${
|
||||
customEmoji!.custom_emoji.alt_text
|
||||
}"/>`;
|
||||
url
|
||||
}" title="${shortcode}" alt="${altText}"/>`;
|
||||
};
|
||||
md.renderer.rules.table_open = function () {
|
||||
return '<table class="table">';
|
||||
|
@ -247,12 +250,14 @@ export function setupMarkdown() {
|
|||
};
|
||||
}
|
||||
|
||||
export function setupEmojiDataModel(custom_emoji_views: CustomEmojiView[]) {
|
||||
export function emojiMartCategories(
|
||||
custom_emoji_views: CustomEmojiView[],
|
||||
): EmojiMartCategory[] {
|
||||
const groupedEmojis = groupBy(
|
||||
custom_emoji_views,
|
||||
x => x.custom_emoji.category,
|
||||
);
|
||||
customEmojis.length = 0;
|
||||
const customEmojis: EmojiMartCategory[] = [];
|
||||
for (const [category, emojis] of Object.entries(groupedEmojis)) {
|
||||
customEmojis.push({
|
||||
id: category,
|
||||
|
@ -265,63 +270,24 @@ export function setupEmojiDataModel(custom_emoji_views: CustomEmojiView[]) {
|
|||
})),
|
||||
});
|
||||
}
|
||||
return customEmojis;
|
||||
}
|
||||
|
||||
export async function setupEmojiDataModel(
|
||||
client: WrappedLemmyHttp = HttpService.client,
|
||||
): Promise<boolean> {
|
||||
const emojisRes = await client.listCustomEmojis({
|
||||
ignore_page_limits: true,
|
||||
});
|
||||
if (emojisRes.state !== "success") {
|
||||
return false;
|
||||
}
|
||||
const custom_emoji_views = emojisRes.data.custom_emojis;
|
||||
customEmojis = emojiMartCategories(custom_emoji_views);
|
||||
customEmojisLookup = new Map(
|
||||
custom_emoji_views.map(view => [view.custom_emoji.shortcode, view]),
|
||||
);
|
||||
}
|
||||
|
||||
export function updateEmojiDataModel(custom_emoji_view: CustomEmojiView) {
|
||||
const emoji: EmojiMartCustomEmoji = {
|
||||
id: custom_emoji_view.custom_emoji.shortcode,
|
||||
name: custom_emoji_view.custom_emoji.shortcode,
|
||||
keywords: custom_emoji_view.keywords.map(x => x.keyword),
|
||||
skins: [{ src: custom_emoji_view.custom_emoji.image_url }],
|
||||
};
|
||||
const categoryIndex = customEmojis.findIndex(
|
||||
x => x.id === custom_emoji_view.custom_emoji.category,
|
||||
);
|
||||
if (categoryIndex === -1) {
|
||||
customEmojis.push({
|
||||
id: custom_emoji_view.custom_emoji.category,
|
||||
name: custom_emoji_view.custom_emoji.category,
|
||||
emojis: [emoji],
|
||||
});
|
||||
} else {
|
||||
const emojiIndex = customEmojis[categoryIndex].emojis.findIndex(
|
||||
x => x.id === custom_emoji_view.custom_emoji.shortcode,
|
||||
);
|
||||
if (emojiIndex === -1) {
|
||||
customEmojis[categoryIndex].emojis.push(emoji);
|
||||
} else {
|
||||
customEmojis[categoryIndex].emojis[emojiIndex] = emoji;
|
||||
}
|
||||
}
|
||||
customEmojisLookup.set(
|
||||
custom_emoji_view.custom_emoji.shortcode,
|
||||
custom_emoji_view,
|
||||
);
|
||||
}
|
||||
|
||||
export function removeFromEmojiDataModel(id: number) {
|
||||
let view: CustomEmojiView | undefined;
|
||||
for (const item of customEmojisLookup.values()) {
|
||||
if (item.custom_emoji.id === id) {
|
||||
view = item;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!view) return;
|
||||
const categoryIndex = customEmojis.findIndex(
|
||||
x => x.id === view?.custom_emoji.category,
|
||||
);
|
||||
const emojiIndex = customEmojis[categoryIndex].emojis.findIndex(
|
||||
x => x.id === view?.custom_emoji.shortcode,
|
||||
);
|
||||
customEmojis[categoryIndex].emojis = customEmojis[
|
||||
categoryIndex
|
||||
].emojis.splice(emojiIndex, 1);
|
||||
|
||||
customEmojisLookup.delete(view?.custom_emoji.shortcode);
|
||||
return true;
|
||||
}
|
||||
|
||||
export function getEmojiMart(
|
||||
|
@ -329,9 +295,9 @@ export function getEmojiMart(
|
|||
customPickerOptions: any = {},
|
||||
) {
|
||||
const pickerOptions = {
|
||||
...customPickerOptions,
|
||||
onEmojiSelect: onEmojiSelect,
|
||||
custom: customEmojis,
|
||||
...customPickerOptions,
|
||||
};
|
||||
return new Picker(pickerOptions);
|
||||
}
|
||||
|
@ -416,7 +382,7 @@ export async function setupTribute() {
|
|||
});
|
||||
}
|
||||
|
||||
interface EmojiMartCategory {
|
||||
export interface EmojiMartCategory {
|
||||
id: string;
|
||||
name: string;
|
||||
emojis: EmojiMartCustomEmoji[];
|
||||
|
|
21
src/shared/utils/app/comment-to-post-sort-type.ts
Normal file
21
src/shared/utils/app/comment-to-post-sort-type.ts
Normal file
|
@ -0,0 +1,21 @@
|
|||
import { CommentSortType, PostSortType } from "lemmy-js-client";
|
||||
|
||||
function assertType<T>(_: T) {}
|
||||
|
||||
export default function commentToPostSortType(
|
||||
sort: CommentSortType,
|
||||
): PostSortType {
|
||||
switch (sort) {
|
||||
case "Hot":
|
||||
case "New":
|
||||
case "Old":
|
||||
case "Controversial":
|
||||
return sort;
|
||||
case "Top":
|
||||
return "TopAll";
|
||||
default: {
|
||||
assertType<never>(sort);
|
||||
return "Hot";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
import { CommentSortType, SortType } from "lemmy-js-client";
|
||||
|
||||
export default function convertCommentSortType(
|
||||
sort: SortType,
|
||||
): CommentSortType {
|
||||
switch (sort) {
|
||||
case "TopAll":
|
||||
case "TopHour":
|
||||
case "TopSixHour":
|
||||
case "TopTwelveHour":
|
||||
case "TopDay":
|
||||
case "TopWeek":
|
||||
case "TopMonth":
|
||||
case "TopThreeMonths":
|
||||
case "TopSixMonths":
|
||||
case "TopNineMonths":
|
||||
case "TopYear": {
|
||||
return "Top";
|
||||
}
|
||||
case "New": {
|
||||
return "New";
|
||||
}
|
||||
case "Hot":
|
||||
case "Active": {
|
||||
return "Hot";
|
||||
}
|
||||
default: {
|
||||
return "Hot";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,11 +1,11 @@
|
|||
import buildCommentsTree from "./build-comments-tree";
|
||||
import { colorList } from "./color-list";
|
||||
import commentToPostSortType from "./comment-to-post-sort-type";
|
||||
import commentsToFlatNodes from "./comments-to-flat-nodes";
|
||||
import communityRSSUrl from "./community-rss-url";
|
||||
import communitySearch from "./community-search";
|
||||
import communitySelectName from "./community-select-name";
|
||||
import communityToChoice from "./community-to-choice";
|
||||
import convertCommentSortType from "./convert-comment-sort-type";
|
||||
import editComment from "./edit-comment";
|
||||
import editCommentReply from "./edit-comment-reply";
|
||||
import editCommentReport from "./edit-comment-report";
|
||||
|
@ -59,12 +59,12 @@ import isAnonymousPath from "./is-anonymous-path";
|
|||
export {
|
||||
buildCommentsTree,
|
||||
colorList,
|
||||
commentToPostSortType,
|
||||
commentsToFlatNodes,
|
||||
communityRSSUrl,
|
||||
communitySearch,
|
||||
communitySelectName,
|
||||
communityToChoice,
|
||||
convertCommentSortType,
|
||||
editComment,
|
||||
editCommentReply,
|
||||
editCommentReport,
|
||||
|
|
|
@ -1,11 +1,8 @@
|
|||
import { GetSiteResponse } from "lemmy-js-client";
|
||||
import { setupEmojiDataModel, setupMarkdown } from "../../markdown";
|
||||
import { setupMarkdown } from "../../markdown";
|
||||
import { UserService } from "../../services";
|
||||
|
||||
export default function initializeSite(site?: GetSiteResponse) {
|
||||
UserService.Instance.myUserInfo = site?.my_user;
|
||||
if (site) {
|
||||
setupEmojiDataModel(site.custom_emojis ?? []);
|
||||
}
|
||||
setupMarkdown();
|
||||
}
|
||||
|
|
|
@ -1,16 +1,39 @@
|
|||
import { CommentSortType, SortType } from "lemmy-js-client";
|
||||
import { CommentSortType, PostSortType } from "lemmy-js-client";
|
||||
|
||||
export default function postToCommentSortType(sort: SortType): CommentSortType {
|
||||
function assertType<T>(_: T) {}
|
||||
|
||||
export default function postToCommentSortType(
|
||||
sort: PostSortType,
|
||||
): CommentSortType {
|
||||
switch (sort) {
|
||||
case "Active":
|
||||
case "Hot":
|
||||
return "Hot";
|
||||
case "New":
|
||||
case "NewComments":
|
||||
return "New";
|
||||
case "Old":
|
||||
return "Old";
|
||||
default:
|
||||
case "Controversial": {
|
||||
return sort;
|
||||
}
|
||||
case "TopAll":
|
||||
case "TopHour":
|
||||
case "TopSixHour":
|
||||
case "TopTwelveHour":
|
||||
case "TopDay":
|
||||
case "TopWeek":
|
||||
case "TopMonth":
|
||||
case "TopThreeMonths":
|
||||
case "TopSixMonths":
|
||||
case "TopNineMonths":
|
||||
case "TopYear": {
|
||||
return "Top";
|
||||
}
|
||||
case "NewComments":
|
||||
case "MostComments":
|
||||
case "Scaled":
|
||||
case "Active": {
|
||||
return "Hot";
|
||||
}
|
||||
default: {
|
||||
assertType<never>(sort);
|
||||
return "Hot";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue