Create post query params (#2515)

* Allow create post page form inputs to be populated from query params

* Fix issue with cross post params.

---------

Co-authored-by: Dessalines <tyhou13@gmx.com>
This commit is contained in:
SleeplessOne1917 2024-06-06 22:56:17 -04:00 committed by GitHub
parent 06b5925e85
commit 7003b564a3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 184 additions and 33 deletions

View file

@ -77,8 +77,6 @@ export class ImageUploadForm extends Component<
i.setState({ loading: true }); i.setState({ loading: true });
HttpService.client.uploadImage({ image }).then(res => { HttpService.client.uploadImage({ image }).then(res => {
console.log("pictrs upload:");
console.log(res);
if (res.state === "success") { if (res.state === "success") {
if (res.data.msg === "ok") { if (res.data.msg === "ok") {
i.props.onUpload(res.data.url as string); i.props.onUpload(res.data.url as string);

View file

@ -47,6 +47,7 @@ interface MarkdownTextAreaProps {
showLanguage?: boolean; showLanguage?: boolean;
hideNavigationWarnings?: boolean; hideNavigationWarnings?: boolean;
onContentChange?(val: string): void; onContentChange?(val: string): void;
onContentBlur?(val: string): void;
onReplyCancel?(): void; onReplyCancel?(): void;
onSubmit?(content: string, languageId?: number): Promise<boolean>; onSubmit?(content: string, languageId?: number): Promise<boolean>;
allLanguages: Language[]; // TODO should probably be nullable allLanguages: Language[]; // TODO should probably be nullable
@ -215,6 +216,7 @@ export class MarkdownTextArea extends Component<
)} )}
value={this.state.content} value={this.state.content}
onInput={linkEvent(this, this.handleContentChange)} onInput={linkEvent(this, this.handleContentChange)}
onBlur={linkEvent(this, this.handleContentBlur)}
onPaste={linkEvent(this, this.handlePaste)} onPaste={linkEvent(this, this.handlePaste)}
onKeyDown={linkEvent(this, this.handleKeyBinds)} onKeyDown={linkEvent(this, this.handleKeyBinds)}
required required
@ -457,8 +459,6 @@ export class MarkdownTextArea extends Component<
async uploadSingleImage(i: MarkdownTextArea, image: File) { async uploadSingleImage(i: MarkdownTextArea, image: File) {
const res = await HttpService.client.uploadImage({ image }); const res = await HttpService.client.uploadImage({ image });
console.log("pictrs upload:");
console.log(res);
if (res.state === "success") { if (res.state === "success") {
if (res.data.msg === "ok") { if (res.data.msg === "ok") {
const imageMarkdown = `![](${res.data.url})`; const imageMarkdown = `![](${res.data.url})`;
@ -496,6 +496,10 @@ export class MarkdownTextArea extends Component<
i.contentChange(); i.contentChange();
} }
handleContentBlur(i: MarkdownTextArea, event: any) {
i.props.onContentBlur?.(event.target.value);
}
// Keybind handler // Keybind handler
// Keybinds inspired by github comment area // Keybinds inspired by github comment area
handleKeyBinds(i: MarkdownTextArea, event: KeyboardEvent) { handleKeyBinds(i: MarkdownTextArea, event: KeyboardEvent) {

View file

@ -497,8 +497,6 @@ export class EmojiForm extends Component<EmojiFormProps, EmojiFormState> {
})); }));
HttpService.client.uploadImage({ image: file }).then(res => { HttpService.client.uploadImage({ image: file }).then(res => {
console.log("pictrs upload:");
console.log(res);
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);

View file

@ -930,7 +930,6 @@ export class Home extends Component<HomeRouteProps, HomeState> {
} }
handleShowHiddenChange(show?: StringBoolean) { handleShowHiddenChange(show?: StringBoolean) {
console.log(`Got ${show}`);
this.updateUrl({ this.updateUrl({
showHidden: show, showHidden: show,
pageCursor: undefined, pageCursor: undefined,

View file

@ -5,8 +5,18 @@ import {
setIsoData, setIsoData,
voteDisplayMode, voteDisplayMode,
} from "@utils/app"; } from "@utils/app";
import { getIdFromString, getQueryParams } from "@utils/helpers"; import {
import { Choice, RouteDataResponse } from "@utils/types"; getIdFromString,
getQueryParams,
getQueryString,
} from "@utils/helpers";
import {
Choice,
CrossPostParams,
QueryParams,
RouteDataResponse,
StringBoolean,
} from "@utils/types";
import { Component } from "inferno"; import { Component } from "inferno";
import { RouteComponentProps } from "inferno-router/dist/Route"; import { RouteComponentProps } from "inferno-router/dist/Route";
import { import {
@ -36,6 +46,13 @@ import { isBrowser } from "@utils/browser";
export interface CreatePostProps { export interface CreatePostProps {
communityId?: number; communityId?: number;
url?: string;
title?: string;
body?: string;
languageId?: number;
nsfw?: StringBoolean;
customThumbnailUrl?: string;
altText?: string;
} }
type CreatePostData = RouteDataResponse<{ type CreatePostData = RouteDataResponse<{
@ -47,6 +64,13 @@ export function getCreatePostQueryParams(source?: string): CreatePostProps {
return getQueryParams<CreatePostProps>( return getQueryParams<CreatePostProps>(
{ {
communityId: getIdFromString, communityId: getIdFromString,
url: (url?: string) => url,
body: (body?: string) => body,
languageId: getIdFromString,
nsfw: (nsfw?: StringBoolean) => nsfw,
customThumbnailUrl: (customThumbnailUrl?: string) => customThumbnailUrl,
title: (title?: string) => title,
altText: (altText?: string) => altText,
}, },
source, source,
); );
@ -92,8 +116,15 @@ export class CreatePost extends Component<
this.handlePostCreate = this.handlePostCreate.bind(this); this.handlePostCreate = this.handlePostCreate.bind(this);
this.handleSelectedCommunityChange = this.handleSelectedCommunityChange =
this.handleSelectedCommunityChange.bind(this); this.handleSelectedCommunityChange.bind(this);
this.handleTitleBlur = this.handleTitleBlur.bind(this);
this.handleUrlBlur = this.handleUrlBlur.bind(this);
this.handleBodyBlur = this.handleBodyBlur.bind(this);
this.handleLanguageChange = this.handleLanguageChange.bind(this);
this.handleNsfwChange = this.handleNsfwChange.bind(this);
this.handleThumbnailUrlBlur = this.handleThumbnailUrlBlur.bind(this);
this.handleAltTextBlur = this.handleAltTextBlur.bind(this);
// Only fetch the data if coming from another route // Only fetch the data if coming from another routeupdate
if (FirstLoadService.isFirstLoad) { if (FirstLoadService.isFirstLoad) {
const { communityResponse: communityRes, initialCommunitiesRes } = const { communityResponse: communityRes, initialCommunitiesRes } =
this.isoData.routeData; this.isoData.routeData;
@ -166,11 +197,31 @@ export class CreatePost extends Component<
render() { render() {
const { selectedCommunityChoice, siteRes, loading } = this.state; const { selectedCommunityChoice, siteRes, loading } = this.state;
const {
body,
communityId,
customThumbnailUrl,
languageId,
title,
nsfw,
url,
} = this.props;
// Only use the name, url, and body from this
const locationState = this.props.history.location.state as const locationState = this.props.history.location.state as
| PostFormParams | CrossPostParams
| undefined; | undefined;
const params: PostFormParams = {
name: title || locationState?.name,
url: url || locationState?.url,
body: body || locationState?.body,
community_id: communityId,
custom_thumbnail: customThumbnailUrl,
language_id: languageId,
nsfw: nsfw === "true",
};
return ( return (
<div className="create-post container-lg"> <div className="create-post container-lg">
<HtmlTags <HtmlTags
@ -182,7 +233,7 @@ export class CreatePost extends Component<
<h1 className="h4 mb-4">{I18NextService.i18n.t("create_post")}</h1> <h1 className="h4 mb-4">{I18NextService.i18n.t("create_post")}</h1>
<PostForm <PostForm
onCreate={this.handlePostCreate} onCreate={this.handlePostCreate}
params={locationState} params={params}
enableDownvotes={enableDownvotes(siteRes)} enableDownvotes={enableDownvotes(siteRes)}
voteDisplayMode={voteDisplayMode(siteRes)} voteDisplayMode={voteDisplayMode(siteRes)}
enableNsfw={enableNsfw(siteRes)} enableNsfw={enableNsfw(siteRes)}
@ -196,6 +247,12 @@ export class CreatePost extends Component<
: [] : []
} }
loading={loading} loading={loading}
onBodyBlur={this.handleBodyBlur}
onLanguageChange={this.handleLanguageChange}
onTitleBlur={this.handleTitleBlur}
onUrlBlur={this.handleUrlBlur}
onThumbnailUrlBlur={this.handleThumbnailUrlBlur}
onNsfwChange={this.handleNsfwChange}
/> />
</div> </div>
</div> </div>
@ -203,23 +260,36 @@ export class CreatePost extends Component<
); );
} }
async updateUrl({ communityId }: Partial<CreatePostProps>) { async updateUrl(props: Partial<CreatePostProps>) {
const locationState = this.props.history.location.state as const {
| PostFormParams body,
| undefined; communityId,
customThumbnailUrl,
languageId,
nsfw,
url,
title,
altText,
} = {
...this.props,
...props,
};
const url = new URL(location.href); const createPostQueryParams: QueryParams<CreatePostProps> = {
body,
communityId: communityId?.toString(),
customThumbnailUrl,
languageId: languageId?.toString(),
title,
nsfw,
url,
altText,
};
const newId = communityId?.toString(); this.props.history.replace({
pathname: "/create_post",
if (newId !== undefined) { search: getQueryString(createPostQueryParams),
url.searchParams.set("communityId", newId); });
} else {
url.searchParams.delete("communityId");
}
// This bypasses the router and doesn't update the query props.
window.history.replaceState(locationState, "", url);
await this.fetchCommunity({ communityId }); await this.fetchCommunity({ communityId });
} }
@ -230,6 +300,34 @@ export class CreatePost extends Component<
}); });
} }
handleTitleBlur(title: string) {
this.updateUrl({ title });
}
handleUrlBlur(url: string) {
this.updateUrl({ url });
}
handleBodyBlur(body: string) {
this.updateUrl({ body });
}
handleLanguageChange(languageId: number) {
this.updateUrl({ languageId });
}
handleNsfwChange(nsfw: StringBoolean) {
this.updateUrl({ nsfw });
}
handleThumbnailUrlBlur(customThumbnailUrl: string) {
this.updateUrl({ customThumbnailUrl });
}
handleAltTextBlur(altText: string) {
this.updateUrl({ altText });
}
async handlePostCreate(form: CreatePostI, bypassNavWarning: () => void) { async handlePostCreate(form: CreatePostI, bypassNavWarning: () => void) {
this.setState({ loading: true }); this.setState({ loading: true });
const res = await HttpService.client.createPost(form); const res = await HttpService.client.createPost(form);

View file

@ -8,7 +8,7 @@ import {
validURL, validURL,
} from "@utils/helpers"; } from "@utils/helpers";
import { isImage } from "@utils/media"; import { isImage } from "@utils/media";
import { Choice } from "@utils/types"; import { Choice, StringBoolean } from "@utils/types";
import autosize from "autosize"; import autosize from "autosize";
import { Component, InfernoNode, createRef, linkEvent } from "inferno"; import { Component, InfernoNode, createRef, linkEvent } from "inferno";
import { Prompt } from "inferno-router"; import { Prompt } from "inferno-router";
@ -63,6 +63,13 @@ interface PostFormProps {
onSelectCommunity?: (choice: Choice) => void; onSelectCommunity?: (choice: Choice) => void;
initialCommunities?: CommunityView[]; initialCommunities?: CommunityView[];
loading: boolean; loading: boolean;
onTitleBlur?: (title: string) => void;
onUrlBlur?: (url: string) => void;
onBodyBlur?: (body: string) => void;
onLanguageChange?: (languageId?: number) => void;
onNsfwChange?: (nsfw: StringBoolean) => void;
onThumbnailUrlBlur?: (thumbnailUrl: string) => void;
onAltTextBlur?: (altText: string) => void;
} }
interface PostFormState { interface PostFormState {
@ -167,8 +174,18 @@ function handlePostUrlChange(i: PostForm, event: any) {
i.fetchPageTitle(); i.fetchPageTitle();
} }
function handlePostUrlBlur(i: PostForm, event: any) {
i.setState({ bypassNavWarning: true });
i.props.onUrlBlur?.(event.target.value);
i.setState({ bypassNavWarning: false });
}
function handlePostNsfwChange(i: PostForm, event: any) { function handlePostNsfwChange(i: PostForm, event: any) {
i.setState(s => ((s.form.nsfw = event.target.checked), s)); i.setState(s => ((s.form.nsfw = event.target.checked), s));
i.setState({ bypassNavWarning: true });
i.props.onNsfwChange?.(event.target.checked ? "true" : "false");
i.setState({ bypassNavWarning: false });
} }
function handleHoneyPotChange(i: PostForm, event: any) { function handleHoneyPotChange(i: PostForm, event: any) {
@ -179,10 +196,22 @@ function handleAltTextChange(i: PostForm, event: any) {
i.setState(s => ((s.form.alt_text = event.target.value), s)); i.setState(s => ((s.form.alt_text = event.target.value), s));
} }
function handleAltTextBlur(i: PostForm, event: any) {
i.setState({ bypassNavWarning: true });
i.props.onAltTextBlur?.(event.target.value);
i.setState({ bypassNavWarning: false });
}
function handleCustomThumbnailChange(i: PostForm, event: any) { function handleCustomThumbnailChange(i: PostForm, event: any) {
i.setState(s => ((s.form.custom_thumbnail = event.target.value), s)); i.setState(s => ((s.form.custom_thumbnail = event.target.value), s));
} }
function handleCustomThumbnailBlur(i: PostForm, event: any) {
i.setState({ bypassNavWarning: true });
i.props.onThumbnailUrlBlur?.(event.target.value);
i.setState({ bypassNavWarning: false });
}
function handleCancel(i: PostForm) { function handleCancel(i: PostForm) {
i.props.onCancel?.(); i.props.onCancel?.();
} }
@ -206,8 +235,6 @@ function handleImageUpload(i: PostForm, event: any) {
i.setState({ imageLoading: true }); i.setState({ imageLoading: true });
HttpService.client.uploadImage({ image: file }).then(res => { HttpService.client.uploadImage({ image: file }).then(res => {
console.log("pictrs upload:");
console.log(res);
if (res.state === "success") { if (res.state === "success") {
if (res.data.msg === "ok") { if (res.data.msg === "ok") {
i.state.form.url = res.data.url; i.state.form.url = res.data.url;
@ -233,6 +260,12 @@ function handlePostNameChange(i: PostForm, event: any) {
i.fetchSimilarPosts(); i.fetchSimilarPosts();
} }
function handlePostNameBlur(i: PostForm, event: any) {
i.setState({ bypassNavWarning: true });
i.props.onTitleBlur?.(event.target.value);
i.setState({ bypassNavWarning: false });
}
function handleImageDelete(i: PostForm) { function handleImageDelete(i: PostForm) {
const { imageDeleteUrl } = i.state; const { imageDeleteUrl } = i.state;
@ -270,6 +303,7 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
this.fetchSimilarPosts = debounce(this.fetchSimilarPosts.bind(this)); this.fetchSimilarPosts = debounce(this.fetchSimilarPosts.bind(this));
this.fetchPageTitle = debounce(this.fetchPageTitle.bind(this)); this.fetchPageTitle = debounce(this.fetchPageTitle.bind(this));
this.handlePostBodyChange = this.handlePostBodyChange.bind(this); this.handlePostBodyChange = this.handlePostBodyChange.bind(this);
this.handlePostBodyBlur = this.handlePostBodyBlur.bind(this);
this.handleLanguageChange = this.handleLanguageChange.bind(this); this.handleLanguageChange = this.handleLanguageChange.bind(this);
this.handleCommunitySelect = this.handleCommunitySelect.bind(this); this.handleCommunitySelect = this.handleCommunitySelect.bind(this);
@ -393,6 +427,7 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
value={this.state.form.name} value={this.state.form.name}
id="post-title" id="post-title"
onInput={linkEvent(this, handlePostNameChange)} onInput={linkEvent(this, handlePostNameChange)}
onBlur={linkEvent(this, handlePostNameBlur)}
className={`form-control ${ className={`form-control ${
!validTitle(this.state.form.name) && "is-invalid" !validTitle(this.state.form.name) && "is-invalid"
}`} }`}
@ -423,6 +458,7 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
className="form-control mb-3" className="form-control mb-3"
value={url} value={url}
onInput={linkEvent(this, handlePostUrlChange)} onInput={linkEvent(this, handlePostUrlChange)}
onBlur={linkEvent(this, handlePostUrlBlur)}
onPaste={linkEvent(this, handleImageUploadPaste)} onPaste={linkEvent(this, handleImageUploadPaste)}
/> />
{this.renderSuggestedTitleCopy()} {this.renderSuggestedTitleCopy()}
@ -538,6 +574,7 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
className="form-control mb-3" className="form-control mb-3"
value={this.state.form.custom_thumbnail} value={this.state.form.custom_thumbnail}
onInput={linkEvent(this, handleCustomThumbnailChange)} onInput={linkEvent(this, handleCustomThumbnailChange)}
onBlur={linkEvent(this, handleCustomThumbnailBlur)}
/> />
</div> </div>
</div> </div>
@ -552,6 +589,7 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
initialContent={this.state.form.body} initialContent={this.state.form.body}
placeholder={I18NextService.i18n.t("optional")} placeholder={I18NextService.i18n.t("optional")}
onContentChange={this.handlePostBodyChange} onContentChange={this.handlePostBodyChange}
onContentBlur={this.handlePostBodyBlur}
allLanguages={this.props.allLanguages} allLanguages={this.props.allLanguages}
siteLanguages={this.props.siteLanguages} siteLanguages={this.props.siteLanguages}
hideNavigationWarnings hideNavigationWarnings
@ -581,6 +619,7 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
id="post-alt-text" id="post-alt-text"
value={this.state.form.alt_text} value={this.state.form.alt_text}
onInput={linkEvent(this, handleAltTextChange)} onInput={linkEvent(this, handleAltTextChange)}
onBlur={linkEvent(this, handleAltTextBlur)}
/> />
</div> </div>
</div> </div>
@ -776,8 +815,18 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
this.setState(s => ((s.form.body = val), s)); this.setState(s => ((s.form.body = val), s));
} }
handlePostBodyBlur(val: string) {
this.setState({ bypassNavWarning: true });
this.props.onBodyBlur?.(val);
this.setState({ bypassNavWarning: false });
}
handleLanguageChange(val: number[]) { handleLanguageChange(val: number[]) {
this.setState(s => ((s.form.language_id = val.at(0)), s)); this.setState(s => ((s.form.language_id = val.at(0)), s));
this.setState({ bypassNavWarning: true });
this.props.onLanguageChange?.(val.at(0));
this.setState({ bypassNavWarning: false });
} }
handleCommunitySearch = debounce(async (text: string) => { handleCommunitySearch = debounce(async (text: string) => {
@ -804,8 +853,8 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
}); });
handleCommunitySelect(choice: Choice) { handleCommunitySelect(choice: Choice) {
if (this.props.onSelectCommunity) { this.setState({ bypassNavWarning: true });
this.props.onSelectCommunity(choice); this.props.onSelectCommunity?.(choice);
} this.setState({ bypassNavWarning: false });
} }
} }

View file

@ -46,6 +46,11 @@ export interface PostFormParams {
name?: string; name?: string;
url?: string; url?: string;
body?: string; body?: string;
nsfw?: boolean;
language_id?: number;
community_id?: number;
custom_thumbnail?: string;
alt_text?: string;
} }
export enum CommentViewType { export enum CommentViewType {