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