diff --git a/src/shared/components/common/image-upload-form.tsx b/src/shared/components/common/image-upload-form.tsx index 9d380fd6..ba77ab47 100644 --- a/src/shared/components/common/image-upload-form.tsx +++ b/src/shared/components/common/image-upload-form.tsx @@ -77,8 +77,6 @@ export class ImageUploadForm extends Component< i.setState({ loading: true }); HttpService.client.uploadImage({ image }).then(res => { - console.log("pictrs upload:"); - console.log(res); if (res.state === "success") { if (res.data.msg === "ok") { i.props.onUpload(res.data.url as string); diff --git a/src/shared/components/common/markdown-textarea.tsx b/src/shared/components/common/markdown-textarea.tsx index d6987e19..bbbda35f 100644 --- a/src/shared/components/common/markdown-textarea.tsx +++ b/src/shared/components/common/markdown-textarea.tsx @@ -47,6 +47,7 @@ interface MarkdownTextAreaProps { showLanguage?: boolean; hideNavigationWarnings?: boolean; onContentChange?(val: string): void; + onContentBlur?(val: string): void; onReplyCancel?(): void; onSubmit?(content: string, languageId?: number): Promise; allLanguages: Language[]; // TODO should probably be nullable @@ -215,6 +216,7 @@ export class MarkdownTextArea extends Component< )} value={this.state.content} onInput={linkEvent(this, this.handleContentChange)} + onBlur={linkEvent(this, this.handleContentBlur)} onPaste={linkEvent(this, this.handlePaste)} onKeyDown={linkEvent(this, this.handleKeyBinds)} required @@ -457,8 +459,6 @@ export class MarkdownTextArea extends Component< async uploadSingleImage(i: MarkdownTextArea, image: File) { const res = await HttpService.client.uploadImage({ image }); - console.log("pictrs upload:"); - console.log(res); if (res.state === "success") { if (res.data.msg === "ok") { const imageMarkdown = `![](${res.data.url})`; @@ -496,6 +496,10 @@ export class MarkdownTextArea extends Component< i.contentChange(); } + handleContentBlur(i: MarkdownTextArea, event: any) { + i.props.onContentBlur?.(event.target.value); + } + // Keybind handler // Keybinds inspired by github comment area handleKeyBinds(i: MarkdownTextArea, event: KeyboardEvent) { diff --git a/src/shared/components/home/emojis-form.tsx b/src/shared/components/home/emojis-form.tsx index f3d98412..e6d299e6 100644 --- a/src/shared/components/home/emojis-form.tsx +++ b/src/shared/components/home/emojis-form.tsx @@ -497,8 +497,6 @@ export class EmojiForm extends Component { })); HttpService.client.uploadImage({ image: file }).then(res => { - console.log("pictrs upload:"); - console.log(res); if (res.state === "success") { if (res.data.msg === "ok") { pictrsDeleteToast(file.name, res.data.delete_url as string); diff --git a/src/shared/components/home/home.tsx b/src/shared/components/home/home.tsx index 22471be9..84aa97a8 100644 --- a/src/shared/components/home/home.tsx +++ b/src/shared/components/home/home.tsx @@ -930,7 +930,6 @@ export class Home extends Component { } handleShowHiddenChange(show?: StringBoolean) { - console.log(`Got ${show}`); this.updateUrl({ showHidden: show, pageCursor: undefined, diff --git a/src/shared/components/post/create-post.tsx b/src/shared/components/post/create-post.tsx index b51d5852..bec74a04 100644 --- a/src/shared/components/post/create-post.tsx +++ b/src/shared/components/post/create-post.tsx @@ -5,8 +5,18 @@ import { setIsoData, voteDisplayMode, } from "@utils/app"; -import { getIdFromString, getQueryParams } from "@utils/helpers"; -import { Choice, RouteDataResponse } from "@utils/types"; +import { + getIdFromString, + getQueryParams, + getQueryString, +} from "@utils/helpers"; +import { + Choice, + CrossPostParams, + QueryParams, + RouteDataResponse, + StringBoolean, +} from "@utils/types"; import { Component } from "inferno"; import { RouteComponentProps } from "inferno-router/dist/Route"; import { @@ -36,6 +46,13 @@ import { isBrowser } from "@utils/browser"; export interface CreatePostProps { communityId?: number; + url?: string; + title?: string; + body?: string; + languageId?: number; + nsfw?: StringBoolean; + customThumbnailUrl?: string; + altText?: string; } type CreatePostData = RouteDataResponse<{ @@ -47,6 +64,13 @@ export function getCreatePostQueryParams(source?: string): CreatePostProps { return getQueryParams( { 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, ); @@ -92,8 +116,15 @@ export class CreatePost extends Component< this.handlePostCreate = this.handlePostCreate.bind(this); this.handleSelectedCommunityChange = 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) { const { communityResponse: communityRes, initialCommunitiesRes } = this.isoData.routeData; @@ -166,11 +197,31 @@ export class CreatePost extends Component< render() { 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 - | PostFormParams + | CrossPostParams | 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 (
{I18NextService.i18n.t("create_post")}
@@ -203,23 +260,36 @@ export class CreatePost extends Component< ); } - async updateUrl({ communityId }: Partial) { - const locationState = this.props.history.location.state as - | PostFormParams - | undefined; + async updateUrl(props: Partial) { + const { + body, + communityId, + customThumbnailUrl, + languageId, + nsfw, + url, + title, + altText, + } = { + ...this.props, + ...props, + }; - const url = new URL(location.href); + const createPostQueryParams: QueryParams = { + body, + communityId: communityId?.toString(), + customThumbnailUrl, + languageId: languageId?.toString(), + title, + nsfw, + url, + altText, + }; - const newId = communityId?.toString(); - - if (newId !== undefined) { - 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); + this.props.history.replace({ + pathname: "/create_post", + search: getQueryString(createPostQueryParams), + }); 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) { this.setState({ loading: true }); const res = await HttpService.client.createPost(form); diff --git a/src/shared/components/post/post-form.tsx b/src/shared/components/post/post-form.tsx index f5fe8b71..880f5e65 100644 --- a/src/shared/components/post/post-form.tsx +++ b/src/shared/components/post/post-form.tsx @@ -8,7 +8,7 @@ import { validURL, } from "@utils/helpers"; import { isImage } from "@utils/media"; -import { Choice } from "@utils/types"; +import { Choice, StringBoolean } from "@utils/types"; import autosize from "autosize"; import { Component, InfernoNode, createRef, linkEvent } from "inferno"; import { Prompt } from "inferno-router"; @@ -63,6 +63,13 @@ interface PostFormProps { onSelectCommunity?: (choice: Choice) => void; initialCommunities?: CommunityView[]; 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 { @@ -167,8 +174,18 @@ function handlePostUrlChange(i: PostForm, event: any) { 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) { 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) { @@ -179,10 +196,22 @@ function handleAltTextChange(i: PostForm, event: any) { 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) { 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) { i.props.onCancel?.(); } @@ -206,8 +235,6 @@ function handleImageUpload(i: PostForm, event: any) { i.setState({ imageLoading: true }); HttpService.client.uploadImage({ image: file }).then(res => { - console.log("pictrs upload:"); - console.log(res); if (res.state === "success") { if (res.data.msg === "ok") { i.state.form.url = res.data.url; @@ -233,6 +260,12 @@ function handlePostNameChange(i: PostForm, event: any) { 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) { const { imageDeleteUrl } = i.state; @@ -270,6 +303,7 @@ export class PostForm extends Component { this.fetchSimilarPosts = debounce(this.fetchSimilarPosts.bind(this)); this.fetchPageTitle = debounce(this.fetchPageTitle.bind(this)); this.handlePostBodyChange = this.handlePostBodyChange.bind(this); + this.handlePostBodyBlur = this.handlePostBodyBlur.bind(this); this.handleLanguageChange = this.handleLanguageChange.bind(this); this.handleCommunitySelect = this.handleCommunitySelect.bind(this); @@ -393,6 +427,7 @@ export class PostForm extends Component { value={this.state.form.name} id="post-title" onInput={linkEvent(this, handlePostNameChange)} + onBlur={linkEvent(this, handlePostNameBlur)} className={`form-control ${ !validTitle(this.state.form.name) && "is-invalid" }`} @@ -423,6 +458,7 @@ export class PostForm extends Component { className="form-control mb-3" value={url} onInput={linkEvent(this, handlePostUrlChange)} + onBlur={linkEvent(this, handlePostUrlBlur)} onPaste={linkEvent(this, handleImageUploadPaste)} /> {this.renderSuggestedTitleCopy()} @@ -538,6 +574,7 @@ export class PostForm extends Component { className="form-control mb-3" value={this.state.form.custom_thumbnail} onInput={linkEvent(this, handleCustomThumbnailChange)} + onBlur={linkEvent(this, handleCustomThumbnailBlur)} /> @@ -552,6 +589,7 @@ export class PostForm extends Component { initialContent={this.state.form.body} placeholder={I18NextService.i18n.t("optional")} onContentChange={this.handlePostBodyChange} + onContentBlur={this.handlePostBodyBlur} allLanguages={this.props.allLanguages} siteLanguages={this.props.siteLanguages} hideNavigationWarnings @@ -581,6 +619,7 @@ export class PostForm extends Component { id="post-alt-text" value={this.state.form.alt_text} onInput={linkEvent(this, handleAltTextChange)} + onBlur={linkEvent(this, handleAltTextBlur)} /> @@ -776,8 +815,18 @@ export class PostForm extends Component { 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[]) { 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) => { @@ -804,8 +853,8 @@ export class PostForm extends Component { }); handleCommunitySelect(choice: Choice) { - if (this.props.onSelectCommunity) { - this.props.onSelectCommunity(choice); - } + this.setState({ bypassNavWarning: true }); + this.props.onSelectCommunity?.(choice); + this.setState({ bypassNavWarning: false }); } } diff --git a/src/shared/interfaces.ts b/src/shared/interfaces.ts index 555b3cfe..8e3f390f 100644 --- a/src/shared/interfaces.ts +++ b/src/shared/interfaces.ts @@ -46,6 +46,11 @@ export interface PostFormParams { name?: string; url?: string; body?: string; + nsfw?: boolean; + language_id?: number; + community_id?: number; + custom_thumbnail?: string; + alt_text?: string; } export enum CommentViewType {