import ProgressBar from "./progress-bar";
interface MarkdownTextAreaProps {
+ /**
+ * Initial content inside the textarea
+ */
initialContent?: string;
+ /**
+ * Numerical ID of the language to select in dropdown
+ */
initialLanguageId?: number;
placeholder?: string;
buttonTitle?: string;
maxLength?: number;
+ /**
+ * Whether this form is for a reply to a Private Message.
+ * If true, a "Cancel" button is shown that will close the reply.
+ */
replyType?: boolean;
focus?: boolean;
disabled?: boolean;
finished?: boolean;
+ /**
+ * Whether to show the language selector
+ */
showLanguage?: boolean;
hideNavigationWarnings?: boolean;
onContentChange?(val: string): void;
<div className="mb-3 row">
<div className="col-12">
<div className="rounded bg-light border">
- <div className="d-flex flex-wrap border-bottom">
+ <div
+ className={classNames("d-flex flex-wrap border-bottom", {
+ "no-click": this.isDisabled,
+ })}
+ >
{this.getFormatButton("bold", this.handleInsertBold)}
{this.getFormatButton("italic", this.handleInsertItalic)}
{this.getFormatButton("link", this.handleInsertLink)}
<EmojiPicker
onEmojiClick={e => this.handleEmoji(this, e)}
- disabled={this.isDisabled}
></EmojiPicker>
- <form className="btn btn-sm text-muted font-weight-bold">
+ <form className="btn btn-sm text-muted fw-bold">
<label
htmlFor={`file-upload-${this.id}`}
+ // TODO: Fix this linting violation
+ // eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex
+ tabIndex={0}
className={`mb-0 ${
UserService.Instance.myUserInfo && "pointer"
}`}
name="file"
className="d-none"
multiple
- disabled={
- !UserService.Instance.myUserInfo || this.isDisabled
- }
+ disabled={!UserService.Instance.myUserInfo}
onChange={linkEvent(this, this.handleImageUpload)}
/>
</form>
{this.getFormatButton("spoiler", this.handleInsertSpoiler)}
<a
href={markdownHelpUrl}
- className="btn btn-sm text-muted font-weight-bold"
+ className="btn btn-sm text-muted fw-bold"
title={I18NextService.i18n.t("formatting_help")}
rel={relTags}
>
<LanguageSelect
iconVersion
allLanguages={this.props.allLanguages}
- // Only set the selected language ID if it exists as an option
- // in the dropdown; otherwise, set it to 0 (Undetermined)
selectedLanguageIds={
- languageId && this.props.siteLanguages.includes(languageId)
- ? [languageId]
- : [0]
+ languageId ? Array.of(languageId) : undefined
}
siteLanguages={this.props.siteLanguages}
onChange={this.handleLanguageChange}
{/* A flex expander */}
<div className="flex-grow-1"></div>
- {this.props.buttonTitle && (
- <button
- type="submit"
- className="btn btn-sm btn-secondary ms-2"
- disabled={this.isDisabled}
- >
- {this.state.loading ? (
- <Spinner />
- ) : (
- <span>{this.props.buttonTitle}</span>
- )}
- </button>
- )}
{this.props.replyType && (
<button
type="button"
{I18NextService.i18n.t("cancel")}
</button>
)}
- {this.state.content && (
+ <button
+ type="button"
+ disabled={!this.state.content}
+ className={classNames("btn btn-sm btn-secondary ms-2", {
+ active: this.state.previewMode,
+ })}
+ onClick={linkEvent(this, this.handlePreviewToggle)}
+ >
+ {this.state.previewMode
+ ? I18NextService.i18n.t("edit")
+ : I18NextService.i18n.t("preview")}
+ </button>
+ {this.props.buttonTitle && (
<button
- className={`btn btn-sm btn-secondary ms-2 ${
- this.state.previewMode && "active"
- }`}
- onClick={linkEvent(this, this.handlePreviewToggle)}
+ type="submit"
+ className="btn btn-sm btn-secondary ms-2"
+ disabled={this.isDisabled || !this.state.content}
>
- {this.state.previewMode
- ? I18NextService.i18n.t("edit")
- : I18NextService.i18n.t("preview")}
+ {this.state.loading && <Spinner className="me-1" />}
+ {this.props.buttonTitle}
</button>
)}
</div>
data-tippy-content={I18NextService.i18n.t(type)}
aria-label={I18NextService.i18n.t(type)}
onClick={linkEvent(this, handleClick)}
- disabled={this.isDisabled}
>
<Icon icon={iconType} classes="icon-inline" />
</button>
const textarea: any = document.getElementById(i.id);
autosize.update(textarea);
pictrsDeleteToast(image.name, res.data.delete_url as string);
+ } else if (res.data.msg === "too_large") {
+ toast(I18NextService.i18n.t("upload_too_large"), "danger");
+ i.setState({ imageUploadStatus: undefined });
+ throw JSON.stringify(res.data);
} else {
throw JSON.stringify(res.data);
}
// Keybind handler
// Keybinds inspired by github comment area
handleKeyBinds(i: MarkdownTextArea, event: KeyboardEvent) {
- if (event.ctrlKey) {
+ if (event.ctrlKey || event.metaKey) {
switch (event.key) {
case "k": {
i.handleInsertLink(i, event);
quoteInsert() {
const textarea: any = document.getElementById(this.id);
const selectedText = window.getSelection()?.toString();
- const { content } = this.state;
+ let { content } = this.state;
if (selectedText) {
const quotedText =
selectedText
.split("\n")
.map(t => `> ${t}`)
.join("\n") + "\n\n";
+
if (!content) {
- this.setState({ content: "" });
+ content = "";
} else {
- this.setState({ content: `${content}\n` });
+ content = `${content}\n\n`;
}
+
this.setState({
content: `${content}${quotedText}`,
});